Merge branch 'fix-all-custom-fields-deleted' into 'develop'
[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.User
9 alias Pleroma.UserInviteToken
10 alias Pleroma.Web.ActivityPub.ActivityPub
11 alias Pleroma.Web.ActivityPub.Relay
12 alias Pleroma.Web.AdminAPI.AccountView
13 alias Pleroma.Web.AdminAPI.Config
14 alias Pleroma.Web.AdminAPI.ConfigView
15 alias Pleroma.Web.AdminAPI.ReportView
16 alias Pleroma.Web.AdminAPI.Search
17 alias Pleroma.Web.CommonAPI
18 alias Pleroma.Web.MastodonAPI.StatusView
19
20 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
21
22 require Logger
23
24 @users_page_size 50
25
26 action_fallback(:errors)
27
28 def user_delete(conn, %{"nickname" => nickname}) do
29 User.get_cached_by_nickname(nickname)
30 |> User.delete()
31
32 conn
33 |> json(nickname)
34 end
35
36 def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
37 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
38 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
39 User.follow(follower, followed)
40 end
41
42 conn
43 |> json("ok")
44 end
45
46 def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
47 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
48 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
49 User.unfollow(follower, followed)
50 end
51
52 conn
53 |> json("ok")
54 end
55
56 def users_create(conn, %{"users" => users}) do
57 changesets =
58 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
59 user_data = %{
60 nickname: nickname,
61 name: nickname,
62 email: email,
63 password: password,
64 password_confirmation: password,
65 bio: "."
66 }
67
68 User.register_changeset(%User{}, user_data, need_confirmation: false)
69 end)
70 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
71 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
72 end)
73
74 case Pleroma.Repo.transaction(changesets) do
75 {:ok, users} ->
76 res =
77 users
78 |> Map.values()
79 |> Enum.map(fn user ->
80 {:ok, user} = User.post_register_action(user)
81 user
82 end)
83 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
84
85 conn
86 |> json(res)
87
88 {:error, id, changeset, _} ->
89 res =
90 Enum.map(changesets.operations, fn
91 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
92 AccountView.render("create-error.json", %{changeset: changeset})
93
94 {_, {:changeset, current_changeset, _}} ->
95 AccountView.render("create-error.json", %{changeset: current_changeset})
96 end)
97
98 conn
99 |> put_status(:conflict)
100 |> json(res)
101 end
102 end
103
104 def user_show(conn, %{"nickname" => nickname}) do
105 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
106 conn
107 |> json(AccountView.render("show.json", %{user: user}))
108 else
109 _ -> {:error, :not_found}
110 end
111 end
112
113 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
114 godmode = params["godmode"] == "true" || params["godmode"] == true
115
116 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
117 {_, page_size} = page_params(params)
118
119 activities =
120 ActivityPub.fetch_user_activities(user, nil, %{
121 "limit" => page_size,
122 "godmode" => godmode
123 })
124
125 conn
126 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
127 else
128 _ -> {:error, :not_found}
129 end
130 end
131
132 def user_toggle_activation(conn, %{"nickname" => nickname}) do
133 user = User.get_cached_by_nickname(nickname)
134
135 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
136
137 conn
138 |> json(AccountView.render("show.json", %{user: updated_user}))
139 end
140
141 def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
142 with {:ok, _} <- User.tag(nicknames, tags),
143 do: json_response(conn, :no_content, "")
144 end
145
146 def untag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
147 with {:ok, _} <- User.untag(nicknames, tags),
148 do: json_response(conn, :no_content, "")
149 end
150
151 def list_users(conn, params) do
152 {page, page_size} = page_params(params)
153 filters = maybe_parse_filters(params["filters"])
154
155 search_params = %{
156 query: params["query"],
157 page: page,
158 page_size: page_size,
159 tags: params["tags"],
160 name: params["name"],
161 email: params["email"]
162 }
163
164 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
165 do:
166 conn
167 |> json(
168 AccountView.render("index.json",
169 users: users,
170 count: count,
171 page_size: page_size
172 )
173 )
174 end
175
176 @filters ~w(local external active deactivated is_admin is_moderator)
177
178 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
179 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
180
181 defp maybe_parse_filters(filters) do
182 filters
183 |> String.split(",")
184 |> Enum.filter(&Enum.member?(@filters, &1))
185 |> Enum.map(&String.to_atom(&1))
186 |> Enum.into(%{}, &{&1, true})
187 end
188
189 def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
190 when permission_group in ["moderator", "admin"] do
191 user = User.get_cached_by_nickname(nickname)
192
193 info =
194 %{}
195 |> Map.put("is_" <> permission_group, true)
196
197 info_cng = User.Info.admin_api_update(user.info, info)
198
199 cng =
200 user
201 |> Ecto.Changeset.change()
202 |> Ecto.Changeset.put_embed(:info, info_cng)
203
204 {:ok, _user} = User.update_and_set_cache(cng)
205
206 json(conn, info)
207 end
208
209 def right_add(conn, _) do
210 render_error(conn, :not_found, "No such permission_group")
211 end
212
213 def right_get(conn, %{"nickname" => nickname}) do
214 user = User.get_cached_by_nickname(nickname)
215
216 conn
217 |> json(%{
218 is_moderator: user.info.is_moderator,
219 is_admin: user.info.is_admin
220 })
221 end
222
223 def right_delete(
224 %{assigns: %{user: %User{:nickname => admin_nickname}}} = conn,
225 %{
226 "permission_group" => permission_group,
227 "nickname" => nickname
228 }
229 )
230 when permission_group in ["moderator", "admin"] do
231 if admin_nickname == nickname do
232 render_error(conn, :forbidden, "You can't revoke your own admin status.")
233 else
234 user = User.get_cached_by_nickname(nickname)
235
236 info =
237 %{}
238 |> Map.put("is_" <> permission_group, false)
239
240 info_cng = User.Info.admin_api_update(user.info, info)
241
242 cng =
243 Ecto.Changeset.change(user)
244 |> Ecto.Changeset.put_embed(:info, info_cng)
245
246 {:ok, _user} = User.update_and_set_cache(cng)
247
248 json(conn, info)
249 end
250 end
251
252 def right_delete(conn, _) do
253 render_error(conn, :not_found, "No such permission_group")
254 end
255
256 def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
257 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
258 %User{} = user <- User.get_cached_by_nickname(nickname),
259 {:ok, _} <- User.deactivate(user, !status),
260 do: json_response(conn, :no_content, "")
261 end
262
263 def relay_follow(conn, %{"relay_url" => target}) do
264 with {:ok, _message} <- Relay.follow(target) do
265 json(conn, target)
266 else
267 _ ->
268 conn
269 |> put_status(500)
270 |> json(target)
271 end
272 end
273
274 def relay_unfollow(conn, %{"relay_url" => target}) do
275 with {:ok, _message} <- Relay.unfollow(target) do
276 json(conn, target)
277 else
278 _ ->
279 conn
280 |> put_status(500)
281 |> json(target)
282 end
283 end
284
285 @doc "Sends registration invite via email"
286 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
287 with true <-
288 Pleroma.Config.get([:instance, :invites_enabled]) &&
289 !Pleroma.Config.get([:instance, :registrations_open]),
290 {:ok, invite_token} <- UserInviteToken.create_invite(),
291 email <-
292 Pleroma.Emails.UserEmail.user_invitation_email(
293 user,
294 invite_token,
295 email,
296 params["name"]
297 ),
298 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
299 json_response(conn, :no_content, "")
300 end
301 end
302
303 @doc "Get a account registeration invite token (base64 string)"
304 def get_invite_token(conn, params) do
305 options = params["invite"] || %{}
306 {:ok, invite} = UserInviteToken.create_invite(options)
307
308 conn
309 |> json(invite.token)
310 end
311
312 @doc "Get list of created invites"
313 def invites(conn, _params) do
314 invites = UserInviteToken.list_invites()
315
316 conn
317 |> json(AccountView.render("invites.json", %{invites: invites}))
318 end
319
320 @doc "Revokes invite by token"
321 def revoke_invite(conn, %{"token" => token}) do
322 with {:ok, invite} <- UserInviteToken.find_by_token(token),
323 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
324 conn
325 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
326 else
327 nil -> {:error, :not_found}
328 end
329 end
330
331 @doc "Get a password reset token (base64 string) for given nickname"
332 def get_password_reset(conn, %{"nickname" => nickname}) do
333 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
334 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
335
336 conn
337 |> json(token.token)
338 end
339
340 def list_reports(conn, params) do
341 params =
342 params
343 |> Map.put("type", "Flag")
344 |> Map.put("skip_preload", true)
345
346 reports =
347 []
348 |> ActivityPub.fetch_activities(params)
349 |> Enum.reverse()
350
351 conn
352 |> put_view(ReportView)
353 |> render("index.json", %{reports: reports})
354 end
355
356 def report_show(conn, %{"id" => id}) do
357 with %Activity{} = report <- Activity.get_by_id(id) do
358 conn
359 |> put_view(ReportView)
360 |> render("show.json", %{report: report})
361 else
362 _ -> {:error, :not_found}
363 end
364 end
365
366 def report_update_state(conn, %{"id" => id, "state" => state}) do
367 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
368 conn
369 |> put_view(ReportView)
370 |> render("show.json", %{report: report})
371 end
372 end
373
374 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
375 with false <- is_nil(params["status"]),
376 %Activity{} <- Activity.get_by_id(id) do
377 params =
378 params
379 |> Map.put("in_reply_to_status_id", id)
380 |> Map.put("visibility", "direct")
381
382 {:ok, activity} = CommonAPI.post(user, params)
383
384 conn
385 |> put_view(StatusView)
386 |> render("status.json", %{activity: activity})
387 else
388 true ->
389 {:param_cast, nil}
390
391 nil ->
392 {:error, :not_found}
393 end
394 end
395
396 def status_update(conn, %{"id" => id} = params) do
397 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
398 conn
399 |> put_view(StatusView)
400 |> render("status.json", %{activity: activity})
401 end
402 end
403
404 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
405 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
406 json(conn, %{})
407 end
408 end
409
410 def migrate_to_db(conn, _params) do
411 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
412 json(conn, %{})
413 end
414
415 def migrate_from_db(conn, _params) do
416 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
417 json(conn, %{})
418 end
419
420 def config_show(conn, _params) do
421 configs = Pleroma.Repo.all(Config)
422
423 conn
424 |> put_view(ConfigView)
425 |> render("index.json", %{configs: configs})
426 end
427
428 def config_update(conn, %{"configs" => configs}) do
429 updated =
430 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
431 updated =
432 Enum.map(configs, fn
433 %{"group" => group, "key" => key, "delete" => "true"} = params ->
434 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
435 config
436
437 %{"group" => group, "key" => key, "value" => value} ->
438 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
439 config
440 end)
441 |> Enum.reject(&is_nil(&1))
442
443 Pleroma.Config.TransferTask.load_and_update_env()
444 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
445 updated
446 else
447 []
448 end
449
450 conn
451 |> put_view(ConfigView)
452 |> render("index.json", %{configs: updated})
453 end
454
455 def errors(conn, {:error, :not_found}) do
456 conn
457 |> put_status(:not_found)
458 |> json(dgettext("errors", "Not found"))
459 end
460
461 def errors(conn, {:error, reason}) do
462 conn
463 |> put_status(:bad_request)
464 |> json(reason)
465 end
466
467 def errors(conn, {:param_cast, _}) do
468 conn
469 |> put_status(:bad_request)
470 |> json(dgettext("errors", "Invalid parameters"))
471 end
472
473 def errors(conn, _) do
474 conn
475 |> put_status(:internal_server_error)
476 |> json(dgettext("errors", "Something went wrong"))
477 end
478
479 defp page_params(params) do
480 {get_page(params["page"]), get_page_size(params["page_size"])}
481 end
482
483 defp get_page(page_string) when is_nil(page_string), do: 1
484
485 defp get_page(page_string) do
486 case Integer.parse(page_string) do
487 {page, _} -> page
488 :error -> 1
489 end
490 end
491
492 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
493
494 defp get_page_size(page_size_string) do
495 case Integer.parse(page_size_string) do
496 {page_size, _} -> page_size
497 :error -> @users_page_size
498 end
499 end
500 end