Merge branch 'benchmarks/favourites_timeline' 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_response",
132 subject: %Activity{} = subject,
133 text: text
134 }) do
135 %ModerationLog{
136 data: %{
137 "actor" => user_to_map(actor),
138 "action" => "report_response",
139 "subject" => report_to_map(subject),
140 "text" => text,
141 "message" => ""
142 }
143 }
144 |> insert_log_entry_with_message()
145 end
146
147 @spec insert_log(%{
148 actor: User,
149 subject: Activity,
150 action: String.t(),
151 sensitive: String.t(),
152 visibility: String.t()
153 }) :: {:ok, ModerationLog} | {:error, any}
154 def insert_log(%{
155 actor: %User{} = actor,
156 action: "status_update",
157 subject: %Activity{} = subject,
158 sensitive: sensitive,
159 visibility: visibility
160 }) do
161 %ModerationLog{
162 data: %{
163 "actor" => user_to_map(actor),
164 "action" => "status_update",
165 "subject" => status_to_map(subject),
166 "sensitive" => sensitive,
167 "visibility" => visibility,
168 "message" => ""
169 }
170 }
171 |> insert_log_entry_with_message()
172 end
173
174 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
175 {:ok, ModerationLog} | {:error, any}
176 def insert_log(%{
177 actor: %User{} = actor,
178 action: "status_delete",
179 subject_id: subject_id
180 }) do
181 %ModerationLog{
182 data: %{
183 "actor" => user_to_map(actor),
184 "action" => "status_delete",
185 "subject_id" => subject_id,
186 "message" => ""
187 }
188 }
189 |> insert_log_entry_with_message()
190 end
191
192 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
193 {:ok, ModerationLog} | {:error, any}
194 def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
195 %ModerationLog{
196 data: %{
197 "actor" => user_to_map(actor),
198 "action" => action,
199 "subject" => user_to_map(subject),
200 "message" => ""
201 }
202 }
203 |> insert_log_entry_with_message()
204 end
205
206 @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
207 {:ok, ModerationLog} | {:error, any}
208 def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
209 subjects = Enum.map(subjects, &user_to_map/1)
210
211 %ModerationLog{
212 data: %{
213 "actor" => user_to_map(actor),
214 "action" => action,
215 "subjects" => subjects,
216 "message" => ""
217 }
218 }
219 |> insert_log_entry_with_message()
220 end
221
222 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
223 {:ok, ModerationLog} | {:error, any}
224 def insert_log(%{
225 actor: %User{} = actor,
226 followed: %User{} = followed,
227 follower: %User{} = follower,
228 action: "follow"
229 }) do
230 %ModerationLog{
231 data: %{
232 "actor" => user_to_map(actor),
233 "action" => "follow",
234 "followed" => user_to_map(followed),
235 "follower" => user_to_map(follower),
236 "message" => ""
237 }
238 }
239 |> insert_log_entry_with_message()
240 end
241
242 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
243 {:ok, ModerationLog} | {:error, any}
244 def insert_log(%{
245 actor: %User{} = actor,
246 followed: %User{} = followed,
247 follower: %User{} = follower,
248 action: "unfollow"
249 }) do
250 %ModerationLog{
251 data: %{
252 "actor" => user_to_map(actor),
253 "action" => "unfollow",
254 "followed" => user_to_map(followed),
255 "follower" => user_to_map(follower),
256 "message" => ""
257 }
258 }
259 |> insert_log_entry_with_message()
260 end
261
262 @spec insert_log(%{
263 actor: User,
264 action: String.t(),
265 nicknames: [String.t()],
266 tags: [String.t()]
267 }) :: {:ok, ModerationLog} | {:error, any}
268 def insert_log(%{
269 actor: %User{} = actor,
270 nicknames: nicknames,
271 tags: tags,
272 action: action
273 }) do
274 %ModerationLog{
275 data: %{
276 "actor" => user_to_map(actor),
277 "nicknames" => nicknames,
278 "tags" => tags,
279 "action" => action,
280 "message" => ""
281 }
282 }
283 |> insert_log_entry_with_message()
284 end
285
286 @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
287 {:ok, ModerationLog} | {:error, any}
288 def insert_log(%{
289 actor: %User{} = actor,
290 action: action,
291 target: target
292 })
293 when action in ["relay_follow", "relay_unfollow"] do
294 %ModerationLog{
295 data: %{
296 "actor" => user_to_map(actor),
297 "action" => action,
298 "target" => target,
299 "message" => ""
300 }
301 }
302 |> insert_log_entry_with_message()
303 end
304
305 @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
306 defp insert_log_entry_with_message(entry) do
307 entry.data["message"]
308 |> put_in(get_log_entry_message(entry))
309 |> Repo.insert()
310 end
311
312 defp user_to_map(users) when is_list(users) do
313 users |> Enum.map(&user_to_map/1)
314 end
315
316 defp user_to_map(%User{} = user) do
317 user
318 |> Map.from_struct()
319 |> Map.take([:id, :nickname])
320 |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
321 |> Map.put("type", "user")
322 end
323
324 defp report_to_map(%Activity{} = report) do
325 %{
326 "type" => "report",
327 "id" => report.id,
328 "state" => report.data["state"]
329 }
330 end
331
332 defp status_to_map(%Activity{} = status) do
333 %{
334 "type" => "status",
335 "id" => status.id
336 }
337 end
338
339 def get_log_entry_message(%ModerationLog{
340 data: %{
341 "actor" => %{"nickname" => actor_nickname},
342 "action" => action,
343 "followed" => %{"nickname" => followed_nickname},
344 "follower" => %{"nickname" => follower_nickname}
345 }
346 }) do
347 "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
348 end
349
350 @spec get_log_entry_message(ModerationLog) :: String.t()
351 def get_log_entry_message(%ModerationLog{
352 data: %{
353 "actor" => %{"nickname" => actor_nickname},
354 "action" => "delete",
355 "subject" => subjects
356 }
357 }) do
358 "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
359 end
360
361 @spec get_log_entry_message(ModerationLog) :: String.t()
362 def get_log_entry_message(%ModerationLog{
363 data: %{
364 "actor" => %{"nickname" => actor_nickname},
365 "action" => "create",
366 "subjects" => subjects
367 }
368 }) do
369 "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
370 end
371
372 @spec get_log_entry_message(ModerationLog) :: String.t()
373 def get_log_entry_message(%ModerationLog{
374 data: %{
375 "actor" => %{"nickname" => actor_nickname},
376 "action" => "activate",
377 "subject" => user
378 }
379 })
380 when is_map(user) do
381 get_log_entry_message(%ModerationLog{
382 data: %{
383 "actor" => %{"nickname" => actor_nickname},
384 "action" => "activate",
385 "subject" => [user]
386 }
387 })
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" => user
407 }
408 })
409 when is_map(user) do
410 get_log_entry_message(%ModerationLog{
411 data: %{
412 "actor" => %{"nickname" => actor_nickname},
413 "action" => "deactivate",
414 "subject" => [user]
415 }
416 })
417 end
418
419 @spec get_log_entry_message(ModerationLog) :: String.t()
420 def get_log_entry_message(%ModerationLog{
421 data: %{
422 "actor" => %{"nickname" => actor_nickname},
423 "action" => "deactivate",
424 "subject" => users
425 }
426 }) do
427 "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
428 end
429
430 @spec get_log_entry_message(ModerationLog) :: String.t()
431 def get_log_entry_message(%ModerationLog{
432 data: %{
433 "actor" => %{"nickname" => actor_nickname},
434 "nicknames" => nicknames,
435 "tags" => tags,
436 "action" => "tag"
437 }
438 }) do
439 tags_string = tags |> Enum.join(", ")
440
441 "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
442 end
443
444 @spec get_log_entry_message(ModerationLog) :: String.t()
445 def get_log_entry_message(%ModerationLog{
446 data: %{
447 "actor" => %{"nickname" => actor_nickname},
448 "nicknames" => nicknames,
449 "tags" => tags,
450 "action" => "untag"
451 }
452 }) do
453 tags_string = tags |> Enum.join(", ")
454
455 "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
456 end
457
458 @spec get_log_entry_message(ModerationLog) :: String.t()
459 def get_log_entry_message(%ModerationLog{
460 data: %{
461 "actor" => %{"nickname" => actor_nickname},
462 "action" => "grant",
463 "subject" => user,
464 "permission" => permission
465 }
466 })
467 when is_map(user) do
468 get_log_entry_message(%ModerationLog{
469 data: %{
470 "actor" => %{"nickname" => actor_nickname},
471 "action" => "grant",
472 "subject" => [user],
473 "permission" => permission
474 }
475 })
476 end
477
478 @spec get_log_entry_message(ModerationLog) :: String.t()
479 def get_log_entry_message(%ModerationLog{
480 data: %{
481 "actor" => %{"nickname" => actor_nickname},
482 "action" => "grant",
483 "subject" => users,
484 "permission" => permission
485 }
486 }) do
487 "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
488 end
489
490 @spec get_log_entry_message(ModerationLog) :: String.t()
491 def get_log_entry_message(%ModerationLog{
492 data: %{
493 "actor" => %{"nickname" => actor_nickname},
494 "action" => "revoke",
495 "subject" => user,
496 "permission" => permission
497 }
498 })
499 when is_map(user) do
500 get_log_entry_message(%ModerationLog{
501 data: %{
502 "actor" => %{"nickname" => actor_nickname},
503 "action" => "revoke",
504 "subject" => [user],
505 "permission" => permission
506 }
507 })
508 end
509
510 @spec get_log_entry_message(ModerationLog) :: String.t()
511 def get_log_entry_message(%ModerationLog{
512 data: %{
513 "actor" => %{"nickname" => actor_nickname},
514 "action" => "revoke",
515 "subject" => users,
516 "permission" => permission
517 }
518 }) do
519 "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
520 end
521
522 @spec get_log_entry_message(ModerationLog) :: String.t()
523 def get_log_entry_message(%ModerationLog{
524 data: %{
525 "actor" => %{"nickname" => actor_nickname},
526 "action" => "relay_follow",
527 "target" => target
528 }
529 }) do
530 "@#{actor_nickname} followed relay: #{target}"
531 end
532
533 @spec get_log_entry_message(ModerationLog) :: String.t()
534 def get_log_entry_message(%ModerationLog{
535 data: %{
536 "actor" => %{"nickname" => actor_nickname},
537 "action" => "relay_unfollow",
538 "target" => target
539 }
540 }) do
541 "@#{actor_nickname} unfollowed relay: #{target}"
542 end
543
544 @spec get_log_entry_message(ModerationLog) :: String.t()
545 def get_log_entry_message(%ModerationLog{
546 data: %{
547 "actor" => %{"nickname" => actor_nickname},
548 "action" => "report_update",
549 "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
550 }
551 }) do
552 "@#{actor_nickname} updated report ##{subject_id} with '#{state}' state"
553 end
554
555 @spec get_log_entry_message(ModerationLog) :: String.t()
556 def get_log_entry_message(%ModerationLog{
557 data: %{
558 "actor" => %{"nickname" => actor_nickname},
559 "action" => "report_response",
560 "subject" => %{"id" => subject_id, "type" => "report"},
561 "text" => text
562 }
563 }) do
564 "@#{actor_nickname} responded with '#{text}' to report ##{subject_id}"
565 end
566
567 @spec get_log_entry_message(ModerationLog) :: String.t()
568 def get_log_entry_message(%ModerationLog{
569 data: %{
570 "actor" => %{"nickname" => actor_nickname},
571 "action" => "status_update",
572 "subject" => %{"id" => subject_id, "type" => "status"},
573 "sensitive" => nil,
574 "visibility" => visibility
575 }
576 }) do
577 "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
578 end
579
580 @spec get_log_entry_message(ModerationLog) :: String.t()
581 def get_log_entry_message(%ModerationLog{
582 data: %{
583 "actor" => %{"nickname" => actor_nickname},
584 "action" => "status_update",
585 "subject" => %{"id" => subject_id, "type" => "status"},
586 "sensitive" => sensitive,
587 "visibility" => nil
588 }
589 }) do
590 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
591 end
592
593 @spec get_log_entry_message(ModerationLog) :: String.t()
594 def get_log_entry_message(%ModerationLog{
595 data: %{
596 "actor" => %{"nickname" => actor_nickname},
597 "action" => "status_update",
598 "subject" => %{"id" => subject_id, "type" => "status"},
599 "sensitive" => sensitive,
600 "visibility" => visibility
601 }
602 }) do
603 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
604 visibility
605 }'"
606 end
607
608 @spec get_log_entry_message(ModerationLog) :: String.t()
609 def get_log_entry_message(%ModerationLog{
610 data: %{
611 "actor" => %{"nickname" => actor_nickname},
612 "action" => "status_delete",
613 "subject_id" => subject_id
614 }
615 }) do
616 "@#{actor_nickname} deleted status ##{subject_id}"
617 end
618
619 @spec get_log_entry_message(ModerationLog) :: String.t()
620 def get_log_entry_message(%ModerationLog{
621 data: %{
622 "actor" => %{"nickname" => actor_nickname},
623 "action" => "force_password_reset",
624 "subject" => subjects
625 }
626 }) do
627 "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
628 end
629
630 @spec get_log_entry_message(ModerationLog) :: String.t()
631 def get_log_entry_message(%ModerationLog{
632 data: %{
633 "actor" => %{"nickname" => actor_nickname},
634 "action" => "confirm_email",
635 "subject" => subjects
636 }
637 }) do
638 "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
639 end
640
641 @spec get_log_entry_message(ModerationLog) :: String.t()
642 def get_log_entry_message(%ModerationLog{
643 data: %{
644 "actor" => %{"nickname" => actor_nickname},
645 "action" => "resend_confirmation_email",
646 "subject" => subjects
647 }
648 }) do
649 "@#{actor_nickname} re-sent confirmation email for users: #{
650 users_to_nicknames_string(subjects)
651 }"
652 end
653
654 defp nicknames_to_string(nicknames) do
655 nicknames
656 |> Enum.map(&"@#{&1}")
657 |> Enum.join(", ")
658 end
659
660 defp users_to_nicknames_string(users) do
661 users
662 |> Enum.map(&"@#{&1["nickname"]}")
663 |> Enum.join(", ")
664 end
665 end