Merge branch 'develop' into feature/reports-groups-and-multiple-state-update
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
7 alias Pleroma.Activity
8 alias Pleroma.ModerationLog
9 alias Pleroma.Plugs.OAuthScopesPlug
10 alias Pleroma.User
11 alias Pleroma.UserInviteToken
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.AdminAPI.Config
17 alias Pleroma.Web.AdminAPI.ConfigView
18 alias Pleroma.Web.AdminAPI.ModerationLogView
19 alias Pleroma.Web.AdminAPI.Report
20 alias Pleroma.Web.AdminAPI.ReportView
21 alias Pleroma.Web.AdminAPI.Search
22 alias Pleroma.Web.CommonAPI
23 alias Pleroma.Web.Endpoint
24 alias Pleroma.Web.MastodonAPI.StatusView
25 alias Pleroma.Web.Router
26
27 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
28
29 require Logger
30
31 plug(
32 OAuthScopesPlug,
33 %{scopes: ["read:accounts"]}
34 when action in [:list_users, :user_show, :right_get, :invites]
35 )
36
37 plug(
38 OAuthScopesPlug,
39 %{scopes: ["write:accounts"]}
40 when action in [
41 :get_invite_token,
42 :revoke_invite,
43 :email_invite,
44 :get_password_reset,
45 :user_follow,
46 :user_unfollow,
47 :user_delete,
48 :users_create,
49 :user_toggle_activation,
50 :user_activate,
51 :user_deactivate,
52 :tag_users,
53 :untag_users,
54 :right_add,
55 :right_delete
56 ]
57 )
58
59 plug(
60 OAuthScopesPlug,
61 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
62 )
63
64 plug(
65 OAuthScopesPlug,
66 %{scopes: ["write:reports"]}
67 when action in [:report_update_state, :report_respond]
68 )
69
70 plug(
71 OAuthScopesPlug,
72 %{scopes: ["read:statuses"]} when action == :list_user_statuses
73 )
74
75 plug(
76 OAuthScopesPlug,
77 %{scopes: ["write:statuses"]}
78 when action in [:status_update, :status_delete]
79 )
80
81 plug(
82 OAuthScopesPlug,
83 %{scopes: ["read"]}
84 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
85 )
86
87 plug(
88 OAuthScopesPlug,
89 %{scopes: ["write"]}
90 when action in [:relay_follow, :relay_unfollow, :config_update]
91 )
92
93 @users_page_size 50
94
95 action_fallback(:errors)
96
97 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
98 user = User.get_cached_by_nickname(nickname)
99 User.delete(user)
100
101 ModerationLog.insert_log(%{
102 actor: admin,
103 subject: [user],
104 action: "delete"
105 })
106
107 conn
108 |> json(nickname)
109 end
110
111 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
112 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
113 User.delete(users)
114
115 ModerationLog.insert_log(%{
116 actor: admin,
117 subject: users,
118 action: "delete"
119 })
120
121 conn
122 |> json(nicknames)
123 end
124
125 def user_follow(%{assigns: %{user: admin}} = conn, %{
126 "follower" => follower_nick,
127 "followed" => followed_nick
128 }) do
129 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
130 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
131 User.follow(follower, followed)
132
133 ModerationLog.insert_log(%{
134 actor: admin,
135 followed: followed,
136 follower: follower,
137 action: "follow"
138 })
139 end
140
141 conn
142 |> json("ok")
143 end
144
145 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
146 "follower" => follower_nick,
147 "followed" => followed_nick
148 }) do
149 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
150 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
151 User.unfollow(follower, followed)
152
153 ModerationLog.insert_log(%{
154 actor: admin,
155 followed: followed,
156 follower: follower,
157 action: "unfollow"
158 })
159 end
160
161 conn
162 |> json("ok")
163 end
164
165 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
166 changesets =
167 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
168 user_data = %{
169 nickname: nickname,
170 name: nickname,
171 email: email,
172 password: password,
173 password_confirmation: password,
174 bio: "."
175 }
176
177 User.register_changeset(%User{}, user_data, need_confirmation: false)
178 end)
179 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
180 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
181 end)
182
183 case Pleroma.Repo.transaction(changesets) do
184 {:ok, users} ->
185 res =
186 users
187 |> Map.values()
188 |> Enum.map(fn user ->
189 {:ok, user} = User.post_register_action(user)
190
191 user
192 end)
193 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
194
195 ModerationLog.insert_log(%{
196 actor: admin,
197 subjects: Map.values(users),
198 action: "create"
199 })
200
201 conn
202 |> json(res)
203
204 {:error, id, changeset, _} ->
205 res =
206 Enum.map(changesets.operations, fn
207 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
208 AccountView.render("create-error.json", %{changeset: changeset})
209
210 {_, {:changeset, current_changeset, _}} ->
211 AccountView.render("create-error.json", %{changeset: current_changeset})
212 end)
213
214 conn
215 |> put_status(:conflict)
216 |> json(res)
217 end
218 end
219
220 def user_show(conn, %{"nickname" => nickname}) do
221 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
222 conn
223 |> put_view(AccountView)
224 |> render("show.json", %{user: user})
225 else
226 _ -> {:error, :not_found}
227 end
228 end
229
230 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
231 godmode = params["godmode"] == "true" || params["godmode"] == true
232
233 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
234 {_, page_size} = page_params(params)
235
236 activities =
237 ActivityPub.fetch_user_activities(user, nil, %{
238 "limit" => page_size,
239 "godmode" => godmode
240 })
241
242 conn
243 |> put_view(StatusView)
244 |> render("index.json", %{activities: activities, as: :activity})
245 else
246 _ -> {:error, :not_found}
247 end
248 end
249
250 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
251 user = User.get_cached_by_nickname(nickname)
252
253 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
254
255 action = if user.deactivated, do: "activate", else: "deactivate"
256
257 ModerationLog.insert_log(%{
258 actor: admin,
259 subject: [user],
260 action: action
261 })
262
263 conn
264 |> put_view(AccountView)
265 |> render("show.json", %{user: updated_user})
266 end
267
268 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
269 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
270 {:ok, updated_users} = User.deactivate(users, false)
271
272 ModerationLog.insert_log(%{
273 actor: admin,
274 subject: users,
275 action: "activate"
276 })
277
278 conn
279 |> put_view(AccountView)
280 |> render("index.json", %{users: Keyword.values(updated_users)})
281 end
282
283 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
284 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
285 {:ok, updated_users} = User.deactivate(users, true)
286
287 ModerationLog.insert_log(%{
288 actor: admin,
289 subject: users,
290 action: "deactivate"
291 })
292
293 conn
294 |> put_view(AccountView)
295 |> render("index.json", %{users: Keyword.values(updated_users)})
296 end
297
298 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
299 with {:ok, _} <- User.tag(nicknames, tags) do
300 ModerationLog.insert_log(%{
301 actor: admin,
302 nicknames: nicknames,
303 tags: tags,
304 action: "tag"
305 })
306
307 json_response(conn, :no_content, "")
308 end
309 end
310
311 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
312 with {:ok, _} <- User.untag(nicknames, tags) do
313 ModerationLog.insert_log(%{
314 actor: admin,
315 nicknames: nicknames,
316 tags: tags,
317 action: "untag"
318 })
319
320 json_response(conn, :no_content, "")
321 end
322 end
323
324 def list_users(conn, params) do
325 {page, page_size} = page_params(params)
326 filters = maybe_parse_filters(params["filters"])
327
328 search_params = %{
329 query: params["query"],
330 page: page,
331 page_size: page_size,
332 tags: params["tags"],
333 name: params["name"],
334 email: params["email"]
335 }
336
337 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
338 {:ok, users, count} <- filter_relay_user(users, count),
339 do:
340 conn
341 |> json(
342 AccountView.render("index.json",
343 users: users,
344 count: count,
345 page_size: page_size
346 )
347 )
348 end
349
350 defp filter_relay_user(users, count) do
351 filtered_users = Enum.reject(users, &relay_user?/1)
352 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
353
354 {:ok, filtered_users, count}
355 end
356
357 defp relay_user?(user) do
358 user.ap_id == Relay.relay_ap_id()
359 end
360
361 @filters ~w(local external active deactivated is_admin is_moderator)
362
363 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
364 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
365
366 defp maybe_parse_filters(filters) do
367 filters
368 |> String.split(",")
369 |> Enum.filter(&Enum.member?(@filters, &1))
370 |> Enum.map(&String.to_atom(&1))
371 |> Enum.into(%{}, &{&1, true})
372 end
373
374 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
375 "permission_group" => permission_group,
376 "nicknames" => nicknames
377 })
378 when permission_group in ["moderator", "admin"] do
379 update = %{:"is_#{permission_group}" => true}
380
381 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
382
383 for u <- users, do: User.admin_api_update(u, update)
384
385 ModerationLog.insert_log(%{
386 action: "grant",
387 actor: admin,
388 subject: users,
389 permission: permission_group
390 })
391
392 json(conn, update)
393 end
394
395 def right_add_multiple(conn, _) do
396 render_error(conn, :not_found, "No such permission_group")
397 end
398
399 def right_add(%{assigns: %{user: admin}} = conn, %{
400 "permission_group" => permission_group,
401 "nickname" => nickname
402 })
403 when permission_group in ["moderator", "admin"] do
404 fields = %{:"is_#{permission_group}" => true}
405
406 {:ok, user} =
407 nickname
408 |> User.get_cached_by_nickname()
409 |> User.admin_api_update(fields)
410
411 ModerationLog.insert_log(%{
412 action: "grant",
413 actor: admin,
414 subject: [user],
415 permission: permission_group
416 })
417
418 json(conn, fields)
419 end
420
421 def right_add(conn, _) do
422 render_error(conn, :not_found, "No such permission_group")
423 end
424
425 def right_get(conn, %{"nickname" => nickname}) do
426 user = User.get_cached_by_nickname(nickname)
427
428 conn
429 |> json(%{
430 is_moderator: user.is_moderator,
431 is_admin: user.is_admin
432 })
433 end
434
435 def right_delete_multiple(
436 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
437 %{
438 "permission_group" => permission_group,
439 "nicknames" => nicknames
440 }
441 )
442 when permission_group in ["moderator", "admin"] do
443 with false <- Enum.member?(nicknames, admin_nickname) do
444 update = %{:"is_#{permission_group}" => false}
445
446 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
447
448 for u <- users, do: User.admin_api_update(u, update)
449
450 ModerationLog.insert_log(%{
451 action: "revoke",
452 actor: admin,
453 subject: users,
454 permission: permission_group
455 })
456
457 json(conn, update)
458 else
459 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
460 end
461 end
462
463 def right_delete_multiple(conn, _) do
464 render_error(conn, :not_found, "No such permission_group")
465 end
466
467 def right_delete(
468 %{assigns: %{user: admin}} = conn,
469 %{
470 "permission_group" => permission_group,
471 "nickname" => nickname
472 }
473 )
474 when permission_group in ["moderator", "admin"] do
475 fields = %{:"is_#{permission_group}" => false}
476
477 {:ok, user} =
478 nickname
479 |> User.get_cached_by_nickname()
480 |> User.admin_api_update(fields)
481
482 ModerationLog.insert_log(%{
483 action: "revoke",
484 actor: admin,
485 subject: [user],
486 permission: permission_group
487 })
488
489 json(conn, fields)
490 end
491
492 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
493 render_error(conn, :forbidden, "You can't revoke your own admin status.")
494 end
495
496 def relay_list(conn, _params) do
497 with {:ok, list} <- Relay.list() do
498 json(conn, %{relays: list})
499 else
500 _ ->
501 conn
502 |> put_status(500)
503 end
504 end
505
506 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
507 with {:ok, _message} <- Relay.follow(target) do
508 ModerationLog.insert_log(%{
509 action: "relay_follow",
510 actor: admin,
511 target: target
512 })
513
514 json(conn, target)
515 else
516 _ ->
517 conn
518 |> put_status(500)
519 |> json(target)
520 end
521 end
522
523 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
524 with {:ok, _message} <- Relay.unfollow(target) do
525 ModerationLog.insert_log(%{
526 action: "relay_unfollow",
527 actor: admin,
528 target: target
529 })
530
531 json(conn, target)
532 else
533 _ ->
534 conn
535 |> put_status(500)
536 |> json(target)
537 end
538 end
539
540 @doc "Sends registration invite via email"
541 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
542 with true <-
543 Pleroma.Config.get([:instance, :invites_enabled]) &&
544 !Pleroma.Config.get([:instance, :registrations_open]),
545 {:ok, invite_token} <- UserInviteToken.create_invite(),
546 email <-
547 Pleroma.Emails.UserEmail.user_invitation_email(
548 user,
549 invite_token,
550 email,
551 params["name"]
552 ),
553 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
554 json_response(conn, :no_content, "")
555 end
556 end
557
558 @doc "Create an account registration invite token"
559 def create_invite_token(conn, params) do
560 opts = %{}
561
562 opts =
563 if params["max_use"],
564 do: Map.put(opts, :max_use, params["max_use"]),
565 else: opts
566
567 opts =
568 if params["expires_at"],
569 do: Map.put(opts, :expires_at, params["expires_at"]),
570 else: opts
571
572 {:ok, invite} = UserInviteToken.create_invite(opts)
573
574 json(conn, AccountView.render("invite.json", %{invite: invite}))
575 end
576
577 @doc "Get list of created invites"
578 def invites(conn, _params) do
579 invites = UserInviteToken.list_invites()
580
581 conn
582 |> put_view(AccountView)
583 |> render("invites.json", %{invites: invites})
584 end
585
586 @doc "Revokes invite by token"
587 def revoke_invite(conn, %{"token" => token}) do
588 with {:ok, invite} <- UserInviteToken.find_by_token(token),
589 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
590 conn
591 |> put_view(AccountView)
592 |> render("invite.json", %{invite: updated_invite})
593 else
594 nil -> {:error, :not_found}
595 end
596 end
597
598 @doc "Get a password reset token (base64 string) for given nickname"
599 def get_password_reset(conn, %{"nickname" => nickname}) do
600 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
601 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
602
603 conn
604 |> json(%{
605 token: token.token,
606 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
607 })
608 end
609
610 @doc "Force password reset for a given user"
611 def force_password_reset(conn, %{"nickname" => nickname}) do
612 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
613
614 User.force_password_reset_async(user)
615
616 json_response(conn, :no_content, "")
617 end
618
619 def list_reports(conn, params) do
620 {page, page_size} = page_params(params)
621
622 conn
623 |> put_view(ReportView)
624 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
625 end
626
627 def list_grouped_reports(conn, _params) do
628 conn
629 |> put_view(ReportView)
630 |> render("index_grouped.json", Utils.get_reports_grouped_by_status())
631 end
632
633 def report_show(conn, %{"id" => id}) do
634 with %Activity{} = report <- Activity.get_by_id(id) do
635 conn
636 |> put_view(ReportView)
637 |> render("show.json", Report.extract_report_info(report))
638 else
639 _ -> {:error, :not_found}
640 end
641 end
642
643 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
644 result =
645 reports
646 |> Enum.map(fn report ->
647 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
648 ModerationLog.insert_log(%{
649 action: "report_update",
650 actor: admin,
651 subject: activity
652 })
653
654 activity
655 else
656 {:error, message} -> %{id: report["id"], error: message}
657 end
658 end)
659
660 case Enum.any?(result, &Map.has_key?(&1, :error)) do
661 true -> json_response(conn, :bad_request, result)
662 false -> json_response(conn, :no_content, "")
663 end
664 end
665
666 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
667 with false <- is_nil(params["status"]),
668 %Activity{} <- Activity.get_by_id(id) do
669 params =
670 params
671 |> Map.put("in_reply_to_status_id", id)
672 |> Map.put("visibility", "direct")
673
674 {:ok, activity} = CommonAPI.post(user, params)
675
676 ModerationLog.insert_log(%{
677 action: "report_response",
678 actor: user,
679 subject: activity,
680 text: params["status"]
681 })
682
683 conn
684 |> put_view(StatusView)
685 |> render("show.json", %{activity: activity})
686 else
687 true ->
688 {:param_cast, nil}
689
690 nil ->
691 {:error, :not_found}
692 end
693 end
694
695 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
696 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
697 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
698
699 ModerationLog.insert_log(%{
700 action: "status_update",
701 actor: admin,
702 subject: activity,
703 sensitive: sensitive,
704 visibility: params["visibility"]
705 })
706
707 conn
708 |> put_view(StatusView)
709 |> render("show.json", %{activity: activity})
710 end
711 end
712
713 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
714 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
715 ModerationLog.insert_log(%{
716 action: "status_delete",
717 actor: user,
718 subject_id: id
719 })
720
721 json(conn, %{})
722 end
723 end
724
725 def list_log(conn, params) do
726 {page, page_size} = page_params(params)
727
728 log =
729 ModerationLog.get_all(%{
730 page: page,
731 page_size: page_size,
732 start_date: params["start_date"],
733 end_date: params["end_date"],
734 user_id: params["user_id"],
735 search: params["search"]
736 })
737
738 conn
739 |> put_view(ModerationLogView)
740 |> render("index.json", %{log: log})
741 end
742
743 def migrate_to_db(conn, _params) do
744 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
745 json(conn, %{})
746 end
747
748 def migrate_from_db(conn, _params) do
749 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
750 json(conn, %{})
751 end
752
753 def config_show(conn, _params) do
754 configs = Pleroma.Repo.all(Config)
755
756 conn
757 |> put_view(ConfigView)
758 |> render("index.json", %{configs: configs})
759 end
760
761 def config_update(conn, %{"configs" => configs}) do
762 updated =
763 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
764 updated =
765 Enum.map(configs, fn
766 %{"group" => group, "key" => key, "delete" => "true"} = params ->
767 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
768 config
769
770 %{"group" => group, "key" => key, "value" => value} ->
771 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
772 config
773 end)
774 |> Enum.reject(&is_nil(&1))
775
776 Pleroma.Config.TransferTask.load_and_update_env()
777 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
778 updated
779 else
780 []
781 end
782
783 conn
784 |> put_view(ConfigView)
785 |> render("index.json", %{configs: updated})
786 end
787
788 def reload_emoji(conn, _params) do
789 Pleroma.Emoji.reload()
790
791 conn |> json("ok")
792 end
793
794 def errors(conn, {:error, :not_found}) do
795 conn
796 |> put_status(:not_found)
797 |> json(dgettext("errors", "Not found"))
798 end
799
800 def errors(conn, {:error, reason}) do
801 conn
802 |> put_status(:bad_request)
803 |> json(reason)
804 end
805
806 def errors(conn, {:param_cast, _}) do
807 conn
808 |> put_status(:bad_request)
809 |> json(dgettext("errors", "Invalid parameters"))
810 end
811
812 def errors(conn, _) do
813 conn
814 |> put_status(:internal_server_error)
815 |> json(dgettext("errors", "Something went wrong"))
816 end
817
818 defp page_params(params) do
819 {get_page(params["page"]), get_page_size(params["page_size"])}
820 end
821
822 defp get_page(page_string) when is_nil(page_string), do: 1
823
824 defp get_page(page_string) do
825 case Integer.parse(page_string) do
826 {page, _} -> page
827 :error -> 1
828 end
829 end
830
831 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
832
833 defp get_page_size(page_size_string) do
834 case Integer.parse(page_size_string) do
835 {page_size, _} -> page_size
836 :error -> @users_page_size
837 end
838 end
839 end