Merge branch 'fix/signup-without-email' into 'develop'
[akkoma] / lib / pleroma / moderation_log.ex
1 defmodule Pleroma.ModerationLog do
2 use Ecto.Schema
3
4 alias Pleroma.Activity
5 alias Pleroma.ModerationLog
6 alias Pleroma.Repo
7 alias Pleroma.User
8
9 import Ecto.Query
10
11 schema "moderation_log" do
12 field(:data, :map)
13
14 timestamps()
15 end
16
17 def get_all(params) do
18 base_query =
19 get_all_query()
20 |> maybe_filter_by_date(params)
21 |> maybe_filter_by_user(params)
22 |> maybe_filter_by_search(params)
23
24 query_with_pagination = base_query |> paginate_query(params)
25
26 %{
27 items: Repo.all(query_with_pagination),
28 count: Repo.aggregate(base_query, :count, :id)
29 }
30 end
31
32 defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query
33
34 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do
35 from(q in query,
36 where: q.inserted_at >= ^parse_datetime(start_date)
37 )
38 end
39
40 defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do
41 from(q in query,
42 where: q.inserted_at <= ^parse_datetime(end_date)
43 )
44 end
45
46 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do
47 from(q in query,
48 where: q.inserted_at >= ^parse_datetime(start_date),
49 where: q.inserted_at <= ^parse_datetime(end_date)
50 )
51 end
52
53 defp maybe_filter_by_user(query, %{user_id: nil}), do: query
54
55 defp maybe_filter_by_user(query, %{user_id: user_id}) do
56 from(q in query,
57 where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
58 )
59 end
60
61 defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
62 do: query
63
64 defp maybe_filter_by_search(query, %{search: search}) do
65 from(q in query,
66 where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
67 )
68 end
69
70 defp paginate_query(query, %{page: page, page_size: page_size}) do
71 from(q in query,
72 limit: ^page_size,
73 offset: ^((page - 1) * page_size)
74 )
75 end
76
77 defp get_all_query do
78 from(q in __MODULE__,
79 order_by: [desc: q.inserted_at]
80 )
81 end
82
83 defp parse_datetime(datetime) do
84 {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime)
85
86 parsed_datetime
87 end
88
89 @spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) ::
90 {:ok, ModerationLog} | {:error, any}
91 def insert_log(%{
92 actor: %User{} = actor,
93 subject: subjects,
94 action: action,
95 permission: permission
96 }) do
97 %ModerationLog{
98 data: %{
99 "actor" => user_to_map(actor),
100 "subject" => user_to_map(subjects),
101 "action" => action,
102 "permission" => permission,
103 "message" => ""
104 }
105 }
106 |> insert_log_entry_with_message()
107 end
108
109 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
110 {:ok, ModerationLog} | {:error, any}
111 def insert_log(%{
112 actor: %User{} = actor,
113 action: "report_update",
114 subject: %Activity{data: %{"type" => "Flag"}} = subject
115 }) do
116 %ModerationLog{
117 data: %{
118 "actor" => user_to_map(actor),
119 "action" => "report_update",
120 "subject" => report_to_map(subject),
121 "message" => ""
122 }
123 }
124 |> insert_log_entry_with_message()
125 end
126
127 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
128 {:ok, ModerationLog} | {:error, any}
129 def insert_log(%{
130 actor: %User{} = actor,
131 action: "report_note",
132 subject: %Activity{} = subject,
133 text: text
134 }) do
135 %ModerationLog{
136 data: %{
137 "actor" => user_to_map(actor),
138 "action" => "report_note",
139 "subject" => report_to_map(subject),
140 "text" => text
141 }
142 }
143 |> insert_log_entry_with_message()
144 end
145
146 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
147 {:ok, ModerationLog} | {:error, any}
148 def insert_log(%{
149 actor: %User{} = actor,
150 action: "report_note_delete",
151 subject: %Activity{} = subject,
152 text: text
153 }) do
154 %ModerationLog{
155 data: %{
156 "actor" => user_to_map(actor),
157 "action" => "report_note_delete",
158 "subject" => report_to_map(subject),
159 "text" => text
160 }
161 }
162 |> insert_log_entry_with_message()
163 end
164
165 @spec insert_log(%{
166 actor: User,
167 subject: Activity,
168 action: String.t(),
169 sensitive: String.t(),
170 visibility: String.t()
171 }) :: {:ok, ModerationLog} | {:error, any}
172 def insert_log(%{
173 actor: %User{} = actor,
174 action: "status_update",
175 subject: %Activity{} = subject,
176 sensitive: sensitive,
177 visibility: visibility
178 }) do
179 %ModerationLog{
180 data: %{
181 "actor" => user_to_map(actor),
182 "action" => "status_update",
183 "subject" => status_to_map(subject),
184 "sensitive" => sensitive,
185 "visibility" => visibility,
186 "message" => ""
187 }
188 }
189 |> insert_log_entry_with_message()
190 end
191
192 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
193 {:ok, ModerationLog} | {:error, any}
194 def insert_log(%{
195 actor: %User{} = actor,
196 action: "status_delete",
197 subject_id: subject_id
198 }) do
199 %ModerationLog{
200 data: %{
201 "actor" => user_to_map(actor),
202 "action" => "status_delete",
203 "subject_id" => subject_id,
204 "message" => ""
205 }
206 }
207 |> insert_log_entry_with_message()
208 end
209
210 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
211 {:ok, ModerationLog} | {:error, any}
212 def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
213 %ModerationLog{
214 data: %{
215 "actor" => user_to_map(actor),
216 "action" => action,
217 "subject" => user_to_map(subject),
218 "message" => ""
219 }
220 }
221 |> insert_log_entry_with_message()
222 end
223
224 @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
225 {:ok, ModerationLog} | {:error, any}
226 def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
227 subjects = Enum.map(subjects, &user_to_map/1)
228
229 %ModerationLog{
230 data: %{
231 "actor" => user_to_map(actor),
232 "action" => action,
233 "subjects" => subjects,
234 "message" => ""
235 }
236 }
237 |> insert_log_entry_with_message()
238 end
239
240 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
241 {:ok, ModerationLog} | {:error, any}
242 def insert_log(%{
243 actor: %User{} = actor,
244 followed: %User{} = followed,
245 follower: %User{} = follower,
246 action: "follow"
247 }) do
248 %ModerationLog{
249 data: %{
250 "actor" => user_to_map(actor),
251 "action" => "follow",
252 "followed" => user_to_map(followed),
253 "follower" => user_to_map(follower),
254 "message" => ""
255 }
256 }
257 |> insert_log_entry_with_message()
258 end
259
260 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
261 {:ok, ModerationLog} | {:error, any}
262 def insert_log(%{
263 actor: %User{} = actor,
264 followed: %User{} = followed,
265 follower: %User{} = follower,
266 action: "unfollow"
267 }) do
268 %ModerationLog{
269 data: %{
270 "actor" => user_to_map(actor),
271 "action" => "unfollow",
272 "followed" => user_to_map(followed),
273 "follower" => user_to_map(follower),
274 "message" => ""
275 }
276 }
277 |> insert_log_entry_with_message()
278 end
279
280 @spec insert_log(%{
281 actor: User,
282 action: String.t(),
283 nicknames: [String.t()],
284 tags: [String.t()]
285 }) :: {:ok, ModerationLog} | {:error, any}
286 def insert_log(%{
287 actor: %User{} = actor,
288 nicknames: nicknames,
289 tags: tags,
290 action: action
291 }) do
292 %ModerationLog{
293 data: %{
294 "actor" => user_to_map(actor),
295 "nicknames" => nicknames,
296 "tags" => tags,
297 "action" => action,
298 "message" => ""
299 }
300 }
301 |> insert_log_entry_with_message()
302 end
303
304 @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
305 {:ok, ModerationLog} | {:error, any}
306 def insert_log(%{
307 actor: %User{} = actor,
308 action: action,
309 target: target
310 })
311 when action in ["relay_follow", "relay_unfollow"] do
312 %ModerationLog{
313 data: %{
314 "actor" => user_to_map(actor),
315 "action" => action,
316 "target" => target,
317 "message" => ""
318 }
319 }
320 |> insert_log_entry_with_message()
321 end
322
323 @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
324 defp insert_log_entry_with_message(entry) do
325 entry.data["message"]
326 |> put_in(get_log_entry_message(entry))
327 |> Repo.insert()
328 end
329
330 defp user_to_map(users) when is_list(users) do
331 users |> Enum.map(&user_to_map/1)
332 end
333
334 defp user_to_map(%User{} = user) do
335 user
336 |> Map.from_struct()
337 |> Map.take([:id, :nickname])
338 |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
339 |> Map.put("type", "user")
340 end
341
342 defp report_to_map(%Activity{} = report) do
343 %{
344 "type" => "report",
345 "id" => report.id,
346 "state" => report.data["state"]
347 }
348 end
349
350 defp status_to_map(%Activity{} = status) do
351 %{
352 "type" => "status",
353 "id" => status.id
354 }
355 end
356
357 def get_log_entry_message(%ModerationLog{
358 data: %{
359 "actor" => %{"nickname" => actor_nickname},
360 "action" => action,
361 "followed" => %{"nickname" => followed_nickname},
362 "follower" => %{"nickname" => follower_nickname}
363 }
364 }) do
365 "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
366 end
367
368 @spec get_log_entry_message(ModerationLog) :: String.t()
369 def get_log_entry_message(%ModerationLog{
370 data: %{
371 "actor" => %{"nickname" => actor_nickname},
372 "action" => "delete",
373 "subject" => subjects
374 }
375 }) do
376 "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
377 end
378
379 @spec get_log_entry_message(ModerationLog) :: String.t()
380 def get_log_entry_message(%ModerationLog{
381 data: %{
382 "actor" => %{"nickname" => actor_nickname},
383 "action" => "create",
384 "subjects" => subjects
385 }
386 }) do
387 "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
388 end
389
390 @spec get_log_entry_message(ModerationLog) :: String.t()
391 def get_log_entry_message(%ModerationLog{
392 data: %{
393 "actor" => %{"nickname" => actor_nickname},
394 "action" => "activate",
395 "subject" => users
396 }
397 }) do
398 "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
399 end
400
401 @spec get_log_entry_message(ModerationLog) :: String.t()
402 def get_log_entry_message(%ModerationLog{
403 data: %{
404 "actor" => %{"nickname" => actor_nickname},
405 "action" => "deactivate",
406 "subject" => users
407 }
408 }) do
409 "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
410 end
411
412 @spec get_log_entry_message(ModerationLog) :: String.t()
413 def get_log_entry_message(%ModerationLog{
414 data: %{
415 "actor" => %{"nickname" => actor_nickname},
416 "nicknames" => nicknames,
417 "tags" => tags,
418 "action" => "tag"
419 }
420 }) do
421 tags_string = tags |> Enum.join(", ")
422
423 "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
424 end
425
426 @spec get_log_entry_message(ModerationLog) :: String.t()
427 def get_log_entry_message(%ModerationLog{
428 data: %{
429 "actor" => %{"nickname" => actor_nickname},
430 "nicknames" => nicknames,
431 "tags" => tags,
432 "action" => "untag"
433 }
434 }) do
435 tags_string = tags |> Enum.join(", ")
436
437 "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
438 end
439
440 @spec get_log_entry_message(ModerationLog) :: String.t()
441 def get_log_entry_message(%ModerationLog{
442 data: %{
443 "actor" => %{"nickname" => actor_nickname},
444 "action" => "grant",
445 "subject" => users,
446 "permission" => permission
447 }
448 }) do
449 "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
450 end
451
452 @spec get_log_entry_message(ModerationLog) :: String.t()
453 def get_log_entry_message(%ModerationLog{
454 data: %{
455 "actor" => %{"nickname" => actor_nickname},
456 "action" => "revoke",
457 "subject" => users,
458 "permission" => permission
459 }
460 }) do
461 "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
462 end
463
464 @spec get_log_entry_message(ModerationLog) :: String.t()
465 def get_log_entry_message(%ModerationLog{
466 data: %{
467 "actor" => %{"nickname" => actor_nickname},
468 "action" => "relay_follow",
469 "target" => target
470 }
471 }) do
472 "@#{actor_nickname} followed relay: #{target}"
473 end
474
475 @spec get_log_entry_message(ModerationLog) :: String.t()
476 def get_log_entry_message(%ModerationLog{
477 data: %{
478 "actor" => %{"nickname" => actor_nickname},
479 "action" => "relay_unfollow",
480 "target" => target
481 }
482 }) do
483 "@#{actor_nickname} unfollowed relay: #{target}"
484 end
485
486 @spec get_log_entry_message(ModerationLog) :: String.t()
487 def get_log_entry_message(%ModerationLog{
488 data: %{
489 "actor" => %{"nickname" => actor_nickname},
490 "action" => "report_update",
491 "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
492 }
493 }) do
494 "@#{actor_nickname} updated report ##{subject_id} with '#{state}' state"
495 end
496
497 @spec get_log_entry_message(ModerationLog) :: String.t()
498 def get_log_entry_message(%ModerationLog{
499 data: %{
500 "actor" => %{"nickname" => actor_nickname},
501 "action" => "report_note",
502 "subject" => %{"id" => subject_id, "type" => "report"},
503 "text" => text
504 }
505 }) do
506 "@#{actor_nickname} added note '#{text}' to report ##{subject_id}"
507 end
508
509 @spec get_log_entry_message(ModerationLog) :: String.t()
510 def get_log_entry_message(%ModerationLog{
511 data: %{
512 "actor" => %{"nickname" => actor_nickname},
513 "action" => "report_note_delete",
514 "subject" => %{"id" => subject_id, "type" => "report"},
515 "text" => text
516 }
517 }) do
518 "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}"
519 end
520
521 @spec get_log_entry_message(ModerationLog) :: String.t()
522 def get_log_entry_message(%ModerationLog{
523 data: %{
524 "actor" => %{"nickname" => actor_nickname},
525 "action" => "status_update",
526 "subject" => %{"id" => subject_id, "type" => "status"},
527 "sensitive" => nil,
528 "visibility" => visibility
529 }
530 }) do
531 "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
532 end
533
534 @spec get_log_entry_message(ModerationLog) :: String.t()
535 def get_log_entry_message(%ModerationLog{
536 data: %{
537 "actor" => %{"nickname" => actor_nickname},
538 "action" => "status_update",
539 "subject" => %{"id" => subject_id, "type" => "status"},
540 "sensitive" => sensitive,
541 "visibility" => nil
542 }
543 }) do
544 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
545 end
546
547 @spec get_log_entry_message(ModerationLog) :: String.t()
548 def get_log_entry_message(%ModerationLog{
549 data: %{
550 "actor" => %{"nickname" => actor_nickname},
551 "action" => "status_update",
552 "subject" => %{"id" => subject_id, "type" => "status"},
553 "sensitive" => sensitive,
554 "visibility" => visibility
555 }
556 }) do
557 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
558 visibility
559 }'"
560 end
561
562 @spec get_log_entry_message(ModerationLog) :: String.t()
563 def get_log_entry_message(%ModerationLog{
564 data: %{
565 "actor" => %{"nickname" => actor_nickname},
566 "action" => "status_delete",
567 "subject_id" => subject_id
568 }
569 }) do
570 "@#{actor_nickname} deleted status ##{subject_id}"
571 end
572
573 @spec get_log_entry_message(ModerationLog) :: String.t()
574 def get_log_entry_message(%ModerationLog{
575 data: %{
576 "actor" => %{"nickname" => actor_nickname},
577 "action" => "force_password_reset",
578 "subject" => subjects
579 }
580 }) do
581 "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
582 end
583
584 @spec get_log_entry_message(ModerationLog) :: String.t()
585 def get_log_entry_message(%ModerationLog{
586 data: %{
587 "actor" => %{"nickname" => actor_nickname},
588 "action" => "confirm_email",
589 "subject" => subjects
590 }
591 }) do
592 "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
593 end
594
595 @spec get_log_entry_message(ModerationLog) :: String.t()
596 def get_log_entry_message(%ModerationLog{
597 data: %{
598 "actor" => %{"nickname" => actor_nickname},
599 "action" => "resend_confirmation_email",
600 "subject" => subjects
601 }
602 }) do
603 "@#{actor_nickname} re-sent confirmation email for users: #{
604 users_to_nicknames_string(subjects)
605 }"
606 end
607
608 defp nicknames_to_string(nicknames) do
609 nicknames
610 |> Enum.map(&"@#{&1}")
611 |> Enum.join(", ")
612 end
613
614 defp users_to_nicknames_string(users) do
615 users
616 |> Enum.map(&"@#{&1["nickname"]}")
617 |> Enum.join(", ")
618 end
619 end