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
14 alias Pleroma.ModerationLog
15 alias Pleroma.Plugs.OAuthScopesPlug
16 alias Pleroma.ReportNote
19 alias Pleroma.UserInviteToken
20 alias Pleroma.Web.ActivityPub.ActivityPub
21 alias Pleroma.Web.ActivityPub.Builder
22 alias Pleroma.Web.ActivityPub.Pipeline
23 alias Pleroma.Web.ActivityPub.Relay
24 alias Pleroma.Web.ActivityPub.Utils
25 alias Pleroma.Web.AdminAPI
26 alias Pleroma.Web.AdminAPI.AccountView
27 alias Pleroma.Web.AdminAPI.ConfigView
28 alias Pleroma.Web.AdminAPI.ModerationLogView
29 alias Pleroma.Web.AdminAPI.Report
30 alias Pleroma.Web.AdminAPI.ReportView
31 alias Pleroma.Web.AdminAPI.Search
32 alias Pleroma.Web.CommonAPI
33 alias Pleroma.Web.Endpoint
34 alias Pleroma.Web.MastodonAPI
35 alias Pleroma.Web.MastodonAPI.AppView
36 alias Pleroma.Web.OAuth.App
37 alias Pleroma.Web.Router
41 @descriptions Pleroma.Docs.JSON.compile()
46 %{scopes: ["read:accounts"], admin: true}
47 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
52 %{scopes: ["write:accounts"], admin: true}
55 :force_password_reset,
58 :user_toggle_activation,
67 :right_delete_multiple,
68 :update_user_credentials
72 plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
76 %{scopes: ["write:invites"], admin: true}
77 when action in [:create_invite_token, :revoke_invite, :email_invite]
82 %{scopes: ["write:follows"], admin: true}
83 when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
88 %{scopes: ["read:reports"], admin: true}
89 when action in [:list_reports, :report_show]
94 %{scopes: ["write:reports"], admin: true}
95 when action in [:reports_update, :report_notes_create, :report_notes_delete]
100 %{scopes: ["read:statuses"], admin: true}
101 when action in [:list_user_statuses, :list_instance_statuses]
106 %{scopes: ["read"], admin: true}
112 :config_descriptions,
119 %{scopes: ["write"], admin: true}
123 :resend_confirmation_email,
133 action_fallback(AdminAPI.FallbackController)
135 def user_delete(conn, %{"nickname" => nickname}) do
136 user_delete(conn, %{"nicknames" => [nickname]})
139 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
142 |> Enum.map(&User.get_cached_by_nickname/1)
145 |> Enum.each(fn user ->
146 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
147 Pipeline.common_pipeline(delete_data, local: true)
150 ModerationLog.insert_log(%{
160 def user_follow(%{assigns: %{user: admin}} = conn, %{
161 "follower" => follower_nick,
162 "followed" => followed_nick
164 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
165 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
166 User.follow(follower, followed)
168 ModerationLog.insert_log(%{
180 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
181 "follower" => follower_nick,
182 "followed" => followed_nick
184 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
185 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
186 User.unfollow(follower, followed)
188 ModerationLog.insert_log(%{
200 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
202 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
208 password_confirmation: password,
212 User.register_changeset(%User{}, user_data, need_confirmation: false)
214 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
215 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
218 case Pleroma.Repo.transaction(changesets) do
223 |> Enum.map(fn user ->
224 {:ok, user} = User.post_register_action(user)
228 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
230 ModerationLog.insert_log(%{
232 subjects: Map.values(users),
239 {:error, id, changeset, _} ->
241 Enum.map(changesets.operations, fn
242 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
243 AccountView.render("create-error.json", %{changeset: changeset})
245 {_, {:changeset, current_changeset, _}} ->
246 AccountView.render("create-error.json", %{changeset: current_changeset})
250 |> put_status(:conflict)
255 def user_show(conn, %{"nickname" => nickname}) do
256 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
258 |> put_view(AccountView)
259 |> render("show.json", %{user: user})
261 _ -> {:error, :not_found}
265 def list_instance_statuses(conn, %{"instance" => instance} = params) do
266 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
267 {page, page_size} = page_params(params)
270 ActivityPub.fetch_statuses(nil, %{
271 "instance" => instance,
272 "limit" => page_size,
273 "offset" => (page - 1) * page_size,
274 "exclude_reblogs" => !with_reblogs && "true"
278 |> put_view(AdminAPI.StatusView)
279 |> render("index.json", %{activities: activities, as: :activity})
282 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
283 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
284 godmode = params["godmode"] == "true" || params["godmode"] == true
286 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
287 {_, page_size} = page_params(params)
290 ActivityPub.fetch_user_activities(user, nil, %{
291 "limit" => page_size,
292 "godmode" => godmode,
293 "exclude_reblogs" => !with_reblogs && "true"
297 |> put_view(MastodonAPI.StatusView)
298 |> render("index.json", %{activities: activities, as: :activity})
300 _ -> {:error, :not_found}
304 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
305 user = User.get_cached_by_nickname(nickname)
307 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
309 action = if user.deactivated, do: "activate", else: "deactivate"
311 ModerationLog.insert_log(%{
318 |> put_view(AccountView)
319 |> render("show.json", %{user: updated_user})
322 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
323 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
324 {:ok, updated_users} = User.deactivate(users, false)
326 ModerationLog.insert_log(%{
333 |> put_view(AccountView)
334 |> render("index.json", %{users: Keyword.values(updated_users)})
337 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
338 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
339 {:ok, updated_users} = User.deactivate(users, true)
341 ModerationLog.insert_log(%{
348 |> put_view(AccountView)
349 |> render("index.json", %{users: Keyword.values(updated_users)})
352 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
353 with {:ok, _} <- User.tag(nicknames, tags) do
354 ModerationLog.insert_log(%{
356 nicknames: nicknames,
361 json_response(conn, :no_content, "")
365 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
366 with {:ok, _} <- User.untag(nicknames, tags) do
367 ModerationLog.insert_log(%{
369 nicknames: nicknames,
374 json_response(conn, :no_content, "")
378 def list_users(conn, params) do
379 {page, page_size} = page_params(params)
380 filters = maybe_parse_filters(params["filters"])
383 query: params["query"],
385 page_size: page_size,
386 tags: params["tags"],
387 name: params["name"],
388 email: params["email"]
391 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
394 AccountView.render("index.json", users: users, count: count, page_size: page_size)
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
580 with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
581 {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
582 {:ok, invite_token} <- UserInviteToken.create_invite(),
584 Pleroma.Emails.UserEmail.user_invitation_email(
590 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
591 json_response(conn, :no_content, "")
593 {:registrations_open, _} ->
594 {:error, "To send invites you need to set the `registrations_open` option to false."}
596 {:invites_enabled, _} ->
597 {:error, "To send invites you need to set the `invites_enabled` option to true."}
601 @doc "Create an account registration invite token"
602 def create_invite_token(conn, params) do
606 if params["max_use"],
607 do: Map.put(opts, :max_use, params["max_use"]),
611 if params["expires_at"],
612 do: Map.put(opts, :expires_at, params["expires_at"]),
615 {:ok, invite} = UserInviteToken.create_invite(opts)
617 json(conn, AccountView.render("invite.json", %{invite: invite}))
620 @doc "Get list of created invites"
621 def invites(conn, _params) do
622 invites = UserInviteToken.list_invites()
625 |> put_view(AccountView)
626 |> render("invites.json", %{invites: invites})
629 @doc "Revokes invite by token"
630 def revoke_invite(conn, %{"token" => token}) do
631 with {:ok, invite} <- UserInviteToken.find_by_token(token),
632 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
634 |> put_view(AccountView)
635 |> render("invite.json", %{invite: updated_invite})
637 nil -> {:error, :not_found}
641 @doc "Get a password reset token (base64 string) for given nickname"
642 def get_password_reset(conn, %{"nickname" => nickname}) do
643 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
644 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
649 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
653 @doc "Force password reset for a given user"
654 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
655 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
657 Enum.each(users, &User.force_password_reset_async/1)
659 ModerationLog.insert_log(%{
662 action: "force_password_reset"
665 json_response(conn, :no_content, "")
668 @doc "Disable mfa for user's account."
669 def disable_mfa(conn, %{"nickname" => nickname}) do
670 case User.get_by_nickname(nickname) do
680 @doc "Show a given user's credentials"
681 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
682 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
684 |> put_view(AccountView)
685 |> render("credentials.json", %{user: user, for: admin})
687 _ -> {:error, :not_found}
691 @doc "Updates a given user"
692 def update_user_credentials(
693 %{assigns: %{user: admin}} = conn,
694 %{"nickname" => nickname} = params
696 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
698 User.update_as_admin(user, params) do
699 ModerationLog.insert_log(%{
702 action: "updated_users"
705 if params["password"] do
706 User.force_password_reset_async(user)
709 ModerationLog.insert_log(%{
712 action: "force_password_reset"
715 json(conn, %{status: "success"})
717 {:error, changeset} ->
718 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
720 json(conn, %{errors: errors})
723 json(conn, %{error: "Unable to update user."})
727 def list_reports(conn, params) do
728 {page, page_size} = page_params(params)
730 reports = Utils.get_reports(params, page, page_size)
733 |> put_view(ReportView)
734 |> render("index.json", %{reports: reports})
737 def report_show(conn, %{"id" => id}) do
738 with %Activity{} = report <- Activity.get_by_id(id) do
740 |> put_view(ReportView)
741 |> render("show.json", Report.extract_report_info(report))
743 _ -> {:error, :not_found}
747 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
750 |> Enum.map(fn report ->
751 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
752 ModerationLog.insert_log(%{
753 action: "report_update",
760 {:error, message} -> %{id: report["id"], error: message}
764 case Enum.any?(result, &Map.has_key?(&1, :error)) do
765 true -> json_response(conn, :bad_request, result)
766 false -> json_response(conn, :no_content, "")
770 def report_notes_create(%{assigns: %{user: user}} = conn, %{
774 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
775 ModerationLog.insert_log(%{
776 action: "report_note",
778 subject: Activity.get_by_id(report_id),
782 json_response(conn, :no_content, "")
784 _ -> json_response(conn, :bad_request, "")
788 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
790 "report_id" => report_id
792 with {:ok, note} <- ReportNote.destroy(note_id) do
793 ModerationLog.insert_log(%{
794 action: "report_note_delete",
796 subject: Activity.get_by_id(report_id),
800 json_response(conn, :no_content, "")
802 _ -> json_response(conn, :bad_request, "")
806 def list_log(conn, params) do
807 {page, page_size} = page_params(params)
810 ModerationLog.get_all(%{
812 page_size: page_size,
813 start_date: params["start_date"],
814 end_date: params["end_date"],
815 user_id: params["user_id"],
816 search: params["search"]
820 |> put_view(ModerationLogView)
821 |> render("index.json", %{log: log})
824 def config_descriptions(conn, _params) do
825 descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
827 json(conn, descriptions)
830 def config_show(conn, %{"only_db" => true}) do
831 with :ok <- configurable_from_database() do
832 configs = Pleroma.Repo.all(ConfigDB)
835 |> put_view(ConfigView)
836 |> render("index.json", %{configs: configs})
840 def config_show(conn, _params) do
841 with :ok <- configurable_from_database() do
842 configs = ConfigDB.get_all_as_keyword()
845 Config.Holder.default_config()
846 |> ConfigDB.merge(configs)
847 |> Enum.map(fn {group, values} ->
848 Enum.map(values, fn {key, value} ->
850 if configs[group][key] do
851 ConfigDB.get_db_keys(configs[group][key], key)
854 db_value = configs[group][key]
857 if !is_nil(db_value) and Keyword.keyword?(db_value) and
858 ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
859 ConfigDB.merge_group(group, key, value, db_value)
865 group: ConfigDB.convert(group),
866 key: ConfigDB.convert(key),
867 value: ConfigDB.convert(merged_value)
870 if db, do: Map.put(setting, :db, db), else: setting
875 json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
879 def config_update(conn, %{"configs" => configs}) do
880 with :ok <- configurable_from_database() do
883 |> Enum.filter(&whitelisted_config?/1)
885 %{"group" => group, "key" => key, "delete" => true} = params ->
886 ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
888 %{"group" => group, "key" => key, "value" => value} ->
889 ConfigDB.update_or_create(%{group: group, key: key, value: value})
891 |> Enum.split_with(fn result -> elem(result, 0) == :error end)
895 |> Enum.map(fn {:ok, config} ->
896 Map.put(config, :db, ConfigDB.get_db_keys(config))
898 |> Enum.split_with(fn config ->
899 Ecto.get_meta(config, :state) == :deleted
902 Config.TransferTask.load_and_update_env(deleted, false)
904 if !Restarter.Pleroma.need_reboot?() do
905 changed_reboot_settings? =
907 |> Enum.any?(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 if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
918 |> put_view(ConfigView)
919 |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
923 def restart(conn, _params) do
924 with :ok <- configurable_from_database() do
925 Restarter.Pleroma.restart(Config.get(:env), 50)
931 def need_reboot(conn, _params) do
932 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
935 defp configurable_from_database do
936 if Config.get(:configurable_from_database) do
939 {:error, "To use this endpoint you need to enable configuration from database."}
943 defp whitelisted_config?(group, key) do
944 if whitelisted_configs = Config.get(:database_config_whitelist) do
945 Enum.any?(whitelisted_configs, fn
946 {whitelisted_group} ->
947 group == inspect(whitelisted_group)
949 {whitelisted_group, whitelisted_key} ->
950 group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
957 defp whitelisted_config?(%{"group" => group, "key" => key}) do
958 whitelisted_config?(group, key)
961 defp whitelisted_config?(%{:group => group} = config) do
962 whitelisted_config?(group, config[:key])
965 def reload_emoji(conn, _params) do
966 Pleroma.Emoji.reload()
971 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
972 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
974 User.toggle_confirmation(users)
976 ModerationLog.insert_log(%{
979 action: "confirm_email"
985 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
986 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
988 User.try_send_confirmation_email(users)
990 ModerationLog.insert_log(%{
993 action: "resend_confirmation_email"
999 def oauth_app_create(conn, params) do
1001 if params["name"] do
1002 Map.put(params, "client_name", params["name"])
1008 case App.create(params) do
1010 AppView.render("show.json", %{app: app, admin: true})
1012 {:error, changeset} ->
1013 App.errors(changeset)
1019 def oauth_app_update(conn, params) do
1021 if params["name"] do
1022 Map.put(params, "client_name", params["name"])
1027 with {:ok, app} <- App.update(params) do
1028 json(conn, AppView.render("show.json", %{app: app, admin: true}))
1030 {:error, changeset} ->
1031 json(conn, App.errors(changeset))
1034 json_response(conn, :bad_request, "")
1038 def oauth_app_list(conn, params) do
1039 {page, page_size} = page_params(params)
1042 client_name: params["name"],
1043 client_id: params["client_id"],
1045 page_size: page_size
1049 if Map.has_key?(params, "trusted") do
1050 Map.put(search_params, :trusted, params["trusted"])
1055 with {:ok, apps, count} <- App.search(search_params) do
1058 AppView.render("index.json",
1061 page_size: page_size,
1068 def oauth_app_delete(conn, params) do
1069 with {:ok, _app} <- App.destroy(params["id"]) do
1070 json_response(conn, :no_content, "")
1072 _ -> json_response(conn, :bad_request, "")
1076 def stats(conn, _) do
1077 count = Stats.get_status_visibility_count()
1080 |> json(%{"status_visibility" => count})
1083 defp page_params(params) do
1084 {get_page(params["page"]), get_page_size(params["page_size"])}
1087 defp get_page(page_string) when is_nil(page_string), do: 1
1089 defp get_page(page_string) do
1090 case Integer.parse(page_string) do
1096 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
1098 defp get_page_size(page_size_string) do
1099 case Integer.parse(page_size_string) do
1100 {page_size, _} -> page_size
1101 :error -> @users_page_size