Merge remote-tracking branch 'pleroma/develop' into notice-routes
[akkoma] / lib / pleroma / web / admin_api / controllers / user_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.UserController do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper,
9 only: [fetch_integer_param: 3]
10
11 alias Pleroma.ModerationLog
12 alias Pleroma.User
13 alias Pleroma.Web.ActivityPub.Builder
14 alias Pleroma.Web.ActivityPub.Pipeline
15 alias Pleroma.Web.AdminAPI
16 alias Pleroma.Web.AdminAPI.Search
17 alias Pleroma.Web.Plugs.OAuthScopesPlug
18
19 @users_page_size 50
20
21 plug(Pleroma.Web.ApiSpec.CastAndValidate)
22
23 plug(
24 OAuthScopesPlug,
25 %{scopes: ["admin:read:accounts"]}
26 when action in [:index, :show]
27 )
28
29 plug(
30 OAuthScopesPlug,
31 %{scopes: ["admin:write:accounts"]}
32 when action in [
33 :delete,
34 :create,
35 :toggle_activation,
36 :activate,
37 :deactivate,
38 :approve
39 ]
40 )
41
42 plug(
43 OAuthScopesPlug,
44 %{scopes: ["admin:write:follows"]}
45 when action in [:follow, :unfollow]
46 )
47
48 plug(:put_view, Pleroma.Web.AdminAPI.AccountView)
49
50 action_fallback(AdminAPI.FallbackController)
51
52 defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
53
54 def delete(conn, %{nickname: nickname}) do
55 conn
56 |> Map.put(:body_params, %{nicknames: [nickname]})
57 |> delete(%{})
58 end
59
60 def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
61 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
62
63 Enum.each(users, fn user ->
64 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
65 Pipeline.common_pipeline(delete_data, local: true)
66 end)
67
68 ModerationLog.insert_log(%{
69 actor: admin,
70 subject: users,
71 action: "delete"
72 })
73
74 json(conn, nicknames)
75 end
76
77 def follow(
78 %{
79 assigns: %{user: admin},
80 body_params: %{
81 follower: follower_nick,
82 followed: followed_nick
83 }
84 } = conn,
85 _
86 ) do
87 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
88 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
89 User.follow(follower, followed)
90
91 ModerationLog.insert_log(%{
92 actor: admin,
93 followed: followed,
94 follower: follower,
95 action: "follow"
96 })
97 end
98
99 json(conn, "ok")
100 end
101
102 def unfollow(
103 %{
104 assigns: %{user: admin},
105 body_params: %{
106 follower: follower_nick,
107 followed: followed_nick
108 }
109 } = conn,
110 _
111 ) do
112 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
113 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
114 User.unfollow(follower, followed)
115
116 ModerationLog.insert_log(%{
117 actor: admin,
118 followed: followed,
119 follower: follower,
120 action: "unfollow"
121 })
122 end
123
124 json(conn, "ok")
125 end
126
127 def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
128 changesets =
129 users
130 |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
131 user_data = %{
132 nickname: nickname,
133 name: nickname,
134 email: email,
135 password: password,
136 password_confirmation: password,
137 bio: "."
138 }
139
140 User.register_changeset(%User{}, user_data, need_confirmation: false)
141 end)
142 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
143 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
144 end)
145
146 case Pleroma.Repo.transaction(changesets) do
147 {:ok, users_map} ->
148 users =
149 users_map
150 |> Map.values()
151 |> Enum.map(fn user ->
152 {:ok, user} = User.post_register_action(user)
153
154 user
155 end)
156
157 ModerationLog.insert_log(%{
158 actor: admin,
159 subjects: users,
160 action: "create"
161 })
162
163 render(conn, "created_many.json", users: users)
164
165 {:error, id, changeset, _} ->
166 changesets =
167 Enum.map(changesets.operations, fn
168 {^id, {:changeset, _current_changeset, _}} ->
169 changeset
170
171 {_, {:changeset, current_changeset, _}} ->
172 current_changeset
173 end)
174
175 conn
176 |> put_status(:conflict)
177 |> render("create_errors.json", changesets: changesets)
178 end
179 end
180
181 def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
182 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
183 render(conn, "show.json", %{user: user})
184 else
185 _ -> {:error, :not_found}
186 end
187 end
188
189 def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
190 user = User.get_cached_by_nickname(nickname)
191
192 {:ok, updated_user} = User.set_activation(user, !user.is_active)
193
194 action = if !user.is_active, do: "activate", else: "deactivate"
195
196 ModerationLog.insert_log(%{
197 actor: admin,
198 subject: [user],
199 action: action
200 })
201
202 render(conn, "show.json", user: updated_user)
203 end
204
205 def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
206 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
207 {:ok, updated_users} = User.set_activation(users, true)
208
209 ModerationLog.insert_log(%{
210 actor: admin,
211 subject: users,
212 action: "activate"
213 })
214
215 render(conn, "index.json", users: Keyword.values(updated_users))
216 end
217
218 def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
219 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
220 {:ok, updated_users} = User.set_activation(users, false)
221
222 ModerationLog.insert_log(%{
223 actor: admin,
224 subject: users,
225 action: "deactivate"
226 })
227
228 render(conn, "index.json", users: Keyword.values(updated_users))
229 end
230
231 def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
232 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
233 {:ok, updated_users} = User.approve(users)
234
235 ModerationLog.insert_log(%{
236 actor: admin,
237 subject: users,
238 action: "approve"
239 })
240
241 render(conn, "index.json", users: updated_users)
242 end
243
244 def index(conn, params) do
245 {page, page_size} = page_params(params)
246 filters = maybe_parse_filters(params[:filters])
247
248 search_params =
249 %{
250 query: params[:query],
251 page: page,
252 page_size: page_size,
253 tags: params[:tags],
254 name: params[:name],
255 email: params[:email],
256 actor_types: params[:actor_types]
257 }
258 |> Map.merge(filters)
259
260 with {:ok, users, count} <- Search.user(search_params) do
261 render(conn, "index.json", users: users, count: count, page_size: page_size)
262 end
263 end
264
265 @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
266
267 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
268 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
269
270 defp maybe_parse_filters(filters) do
271 filters
272 |> String.split(",")
273 |> Enum.filter(&Enum.member?(@filters, &1))
274 |> Map.new(&{String.to_existing_atom(&1), true})
275 end
276
277 defp page_params(params) do
278 {
279 fetch_integer_param(params, :page, 1),
280 fetch_integer_param(params, :page_size, @users_page_size)
281 }
282 end
283 end