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} ->
719 Enum.reduce(changeset.errors, %{}, fn
720 {key, {error, _}}, acc ->
721 Map.put(acc, key, error)
724 json(conn, %{errors: errors})
727 json(conn, %{error: "Unable to update user."})
731 def list_reports(conn, params) do
732 {page, page_size} = page_params(params)
734 reports = Utils.get_reports(params, page, page_size)
737 |> put_view(ReportView)
738 |> render("index.json", %{reports: reports})
741 def report_show(conn, %{"id" => id}) do
742 with %Activity{} = report <- Activity.get_by_id(id) do
744 |> put_view(ReportView)
745 |> render("show.json", Report.extract_report_info(report))
747 _ -> {:error, :not_found}
751 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
754 |> Enum.map(fn report ->
755 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
756 ModerationLog.insert_log(%{
757 action: "report_update",
764 {:error, message} -> %{id: report["id"], error: message}
768 case Enum.any?(result, &Map.has_key?(&1, :error)) do
769 true -> json_response(conn, :bad_request, result)
770 false -> json_response(conn, :no_content, "")
774 def report_notes_create(%{assigns: %{user: user}} = conn, %{
778 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
779 ModerationLog.insert_log(%{
780 action: "report_note",
782 subject: Activity.get_by_id(report_id),
786 json_response(conn, :no_content, "")
788 _ -> json_response(conn, :bad_request, "")
792 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
794 "report_id" => report_id
796 with {:ok, note} <- ReportNote.destroy(note_id) do
797 ModerationLog.insert_log(%{
798 action: "report_note_delete",
800 subject: Activity.get_by_id(report_id),
804 json_response(conn, :no_content, "")
806 _ -> json_response(conn, :bad_request, "")
810 def list_log(conn, params) do
811 {page, page_size} = page_params(params)
814 ModerationLog.get_all(%{
816 page_size: page_size,
817 start_date: params["start_date"],
818 end_date: params["end_date"],
819 user_id: params["user_id"],
820 search: params["search"]
824 |> put_view(ModerationLogView)
825 |> render("index.json", %{log: log})
828 def config_descriptions(conn, _params) do
829 descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
831 json(conn, descriptions)
834 def config_show(conn, %{"only_db" => true}) do
835 with :ok <- configurable_from_database() do
836 configs = Pleroma.Repo.all(ConfigDB)
839 |> put_view(ConfigView)
840 |> render("index.json", %{configs: configs})
844 def config_show(conn, _params) do
845 with :ok <- configurable_from_database() do
846 configs = ConfigDB.get_all_as_keyword()
849 Config.Holder.default_config()
850 |> ConfigDB.merge(configs)
851 |> Enum.map(fn {group, values} ->
852 Enum.map(values, fn {key, value} ->
854 if configs[group][key] do
855 ConfigDB.get_db_keys(configs[group][key], key)
858 db_value = configs[group][key]
861 if !is_nil(db_value) and Keyword.keyword?(db_value) and
862 ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
863 ConfigDB.merge_group(group, key, value, db_value)
869 group: ConfigDB.convert(group),
870 key: ConfigDB.convert(key),
871 value: ConfigDB.convert(merged_value)
874 if db, do: Map.put(setting, :db, db), else: setting
879 json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
883 def config_update(conn, %{"configs" => configs}) do
884 with :ok <- configurable_from_database() do
887 |> Enum.filter(&whitelisted_config?/1)
889 %{"group" => group, "key" => key, "delete" => true} = params ->
890 ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
892 %{"group" => group, "key" => key, "value" => value} ->
893 ConfigDB.update_or_create(%{group: group, key: key, value: value})
895 |> Enum.split_with(fn result -> elem(result, 0) == :error end)
899 |> Enum.map(fn {:ok, config} ->
900 Map.put(config, :db, ConfigDB.get_db_keys(config))
902 |> Enum.split_with(fn config ->
903 Ecto.get_meta(config, :state) == :deleted
906 Config.TransferTask.load_and_update_env(deleted, false)
908 if !Restarter.Pleroma.need_reboot?() do
909 changed_reboot_settings? =
911 |> Enum.any?(fn config ->
912 group = ConfigDB.from_string(config.group)
913 key = ConfigDB.from_string(config.key)
914 value = ConfigDB.from_binary(config.value)
915 Config.TransferTask.pleroma_need_restart?(group, key, value)
918 if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
922 |> put_view(ConfigView)
923 |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
927 def restart(conn, _params) do
928 with :ok <- configurable_from_database() do
929 Restarter.Pleroma.restart(Config.get(:env), 50)
935 def need_reboot(conn, _params) do
936 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
939 defp configurable_from_database do
940 if Config.get(:configurable_from_database) do
943 {:error, "To use this endpoint you need to enable configuration from database."}
947 defp whitelisted_config?(group, key) do
948 if whitelisted_configs = Config.get(:database_config_whitelist) do
949 Enum.any?(whitelisted_configs, fn
950 {whitelisted_group} ->
951 group == inspect(whitelisted_group)
953 {whitelisted_group, whitelisted_key} ->
954 group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
961 defp whitelisted_config?(%{"group" => group, "key" => key}) do
962 whitelisted_config?(group, key)
965 defp whitelisted_config?(%{:group => group} = config) do
966 whitelisted_config?(group, config[:key])
969 def reload_emoji(conn, _params) do
970 Pleroma.Emoji.reload()
975 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
976 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
978 User.toggle_confirmation(users)
980 ModerationLog.insert_log(%{
983 action: "confirm_email"
989 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
990 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
992 User.try_send_confirmation_email(users)
994 ModerationLog.insert_log(%{
997 action: "resend_confirmation_email"
1003 def oauth_app_create(conn, params) do
1005 if params["name"] do
1006 Map.put(params, "client_name", params["name"])
1012 case App.create(params) do
1014 AppView.render("show.json", %{app: app, admin: true})
1016 {:error, changeset} ->
1017 App.errors(changeset)
1023 def oauth_app_update(conn, params) do
1025 if params["name"] do
1026 Map.put(params, "client_name", params["name"])
1031 with {:ok, app} <- App.update(params) do
1032 json(conn, AppView.render("show.json", %{app: app, admin: true}))
1034 {:error, changeset} ->
1035 json(conn, App.errors(changeset))
1038 json_response(conn, :bad_request, "")
1042 def oauth_app_list(conn, params) do
1043 {page, page_size} = page_params(params)
1046 client_name: params["name"],
1047 client_id: params["client_id"],
1049 page_size: page_size
1053 if Map.has_key?(params, "trusted") do
1054 Map.put(search_params, :trusted, params["trusted"])
1059 with {:ok, apps, count} <- App.search(search_params) do
1062 AppView.render("index.json",
1065 page_size: page_size,
1072 def oauth_app_delete(conn, params) do
1073 with {:ok, _app} <- App.destroy(params["id"]) do
1074 json_response(conn, :no_content, "")
1076 _ -> json_response(conn, :bad_request, "")
1080 def stats(conn, _) do
1081 count = Stats.get_status_visibility_count()
1084 |> json(%{"status_visibility" => count})
1087 defp page_params(params) do
1088 {get_page(params["page"]), get_page_size(params["page_size"])}
1091 defp get_page(page_string) when is_nil(page_string), do: 1
1093 defp get_page(page_string) do
1094 case Integer.parse(page_string) do
1100 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
1102 defp get_page_size(page_size_string) do
1103 case Integer.parse(page_size_string) do
1104 {page_size, _} -> page_size
1105 :error -> @users_page_size