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