Add ability to search moderation logs

This commit is contained in:
Maxim Filippov 2019-08-31 00:57:15 +03:00
parent 5e4fde1d3d
commit f182f0f6bd
4 changed files with 220 additions and 90 deletions

View file

@ -18,6 +18,8 @@ def get_all(params) do
params params
|> get_all_query() |> get_all_query()
|> maybe_filter_by_date(params) |> maybe_filter_by_date(params)
|> maybe_filter_by_user(params)
|> maybe_filter_by_search(params)
|> Repo.all() |> Repo.all()
end end
@ -42,6 +44,23 @@ defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date})
) )
end end
defp maybe_filter_by_user(query, %{user_id: nil}), do: query
defp maybe_filter_by_user(query, %{user_id: user_id}) do
from(q in query,
where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
)
end
defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
do: query
defp maybe_filter_by_search(query, %{search: search}) do
from(q in query,
where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
)
end
defp get_all_query(%{page: page, page_size: page_size}) do defp get_all_query(%{page: page, page_size: page_size}) do
from(q in __MODULE__, from(q in __MODULE__,
order_by: [desc: q.inserted_at], order_by: [desc: q.inserted_at],
@ -56,52 +75,71 @@ defp parse_datetime(datetime) do
parsed_datetime parsed_datetime
end end
@spec insert_log(%{actor: User, subject: User, action: String.t(), permission: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
subject: %User{} = subject, subject: %User{} = subject,
action: action, action: action,
permission: permission permission: permission
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
subject: user_to_map(subject), "subject" => user_to_map(subject),
action: action, "action" => action,
permission: permission "permission" => permission,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
action: "report_update", action: "report_update",
subject: %Activity{data: %{"type" => "Flag"}} = subject subject: %Activity{data: %{"type" => "Flag"}} = subject
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "report_update", "action" => "report_update",
subject: report_to_map(subject) "subject" => report_to_map(subject),
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
action: "report_response", action: "report_response",
subject: %Activity{} = subject, subject: %Activity{} = subject,
text: text text: text
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "report_response", "action" => "report_response",
subject: report_to_map(subject), "subject" => report_to_map(subject),
text: text "text" => text,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{
actor: User,
subject: Activity,
action: String.t(),
sensitive: String.t(),
visibility: String.t()
}) :: {:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
action: "status_update", action: "status_update",
@ -109,41 +147,49 @@ def insert_log(%{
sensitive: sensitive, sensitive: sensitive,
visibility: visibility visibility: visibility
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "status_update", "action" => "status_update",
subject: status_to_map(subject), "subject" => status_to_map(subject),
sensitive: sensitive, "sensitive" => sensitive,
visibility: visibility "visibility" => visibility,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
action: "status_delete", action: "status_delete",
subject_id: subject_id subject_id: subject_id
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "status_delete", "action" => "status_delete",
subject_id: subject_id "subject_id" => subject_id,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subject: User, action: String.t()}) :: @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
{:ok, ModerationLog} | {:error, any} {:ok, ModerationLog} | {:error, any}
def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: action, "action" => action,
subject: user_to_map(subject) "subject" => user_to_map(subject),
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, subjects: [User], action: String.t()}) :: @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
@ -151,97 +197,124 @@ def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
subjects = Enum.map(subjects, &user_to_map/1) subjects = Enum.map(subjects, &user_to_map/1)
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: action, "action" => action,
subjects: subjects "subjects" => subjects,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
followed: %User{} = followed, followed: %User{} = followed,
follower: %User{} = follower, follower: %User{} = follower,
action: "follow" action: "follow"
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "follow", "action" => "follow",
followed: user_to_map(followed), "followed" => user_to_map(followed),
follower: user_to_map(follower) "follower" => user_to_map(follower),
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
followed: %User{} = followed, followed: %User{} = followed,
follower: %User{} = follower, follower: %User{} = follower,
action: "unfollow" action: "unfollow"
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: "unfollow", "action" => "unfollow",
followed: user_to_map(followed), "followed" => user_to_map(followed),
follower: user_to_map(follower) "follower" => user_to_map(follower),
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), nicknames: [String.t()], tags: [String.t()]}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
nicknames: nicknames, nicknames: nicknames,
tags: tags, tags: tags,
action: action action: action
}) do }) do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
nicknames: nicknames, "nicknames" => nicknames,
tags: tags, "tags" => tags,
action: action "action" => action,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end end
@spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
{:ok, ModerationLog} | {:error, any}
def insert_log(%{ def insert_log(%{
actor: %User{} = actor, actor: %User{} = actor,
action: action, action: action,
target: target target: target
}) })
when action in ["relay_follow", "relay_unfollow"] do when action in ["relay_follow", "relay_unfollow"] do
Repo.insert(%ModerationLog{ %ModerationLog{
data: %{ data: %{
actor: user_to_map(actor), "actor" => user_to_map(actor),
action: action, "action" => action,
target: target "target" => target,
"message" => ""
} }
}) }
|> insert_log_entry_with_message()
end
@spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
defp insert_log_entry_with_message(entry) do
entry.data["message"]
|> put_in(get_log_entry_message(entry))
|> Repo.insert()
end end
defp user_to_map(%User{} = user) do defp user_to_map(%User{} = user) do
user user
|> Map.from_struct() |> Map.from_struct()
|> Map.take([:id, :nickname]) |> Map.take([:id, :nickname])
|> Map.put(:type, "user") |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
|> Map.put("type", "user")
end end
defp report_to_map(%Activity{} = report) do defp report_to_map(%Activity{} = report) do
%{ %{
type: "report", "type" => "report",
id: report.id, "id" => report.id,
state: report.data["state"] "state" => report.data["state"]
} }
end end
defp status_to_map(%Activity{} = status) do defp status_to_map(%Activity{} = status) do
%{ %{
type: "status", "type" => "status",
id: status.id "id" => status.id
} }
end end

View file

@ -544,7 +544,9 @@ def list_log(conn, params) do
page: page, page: page,
page_size: page_size, page_size: page_size,
start_date: params["start_date"], start_date: params["start_date"],
end_date: params["end_date"] end_date: params["end_date"],
user_id: params["user_id"],
search: params["search"]
}) })
conn conn

View file

@ -30,8 +30,7 @@ test "logging user deletion by moderator", %{moderator: moderator, subject1: sub
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] == "@#{moderator.nickname} deleted user @#{subject1.nickname}"
"@#{moderator.nickname} deleted user @#{subject1.nickname}"
end end
test "logging user creation by moderator", %{ test "logging user creation by moderator", %{
@ -48,7 +47,7 @@ test "logging user creation by moderator", %{
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} created users: @#{subject1.nickname}, @#{subject2.nickname}" "@#{moderator.nickname} created users: @#{subject1.nickname}, @#{subject2.nickname}"
end end
@ -63,7 +62,7 @@ test "logging user follow by admin", %{admin: admin, subject1: subject1, subject
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{admin.nickname} made @#{subject2.nickname} follow @#{subject1.nickname}" "@#{admin.nickname} made @#{subject2.nickname} follow @#{subject1.nickname}"
end end
@ -78,7 +77,7 @@ test "logging user unfollow by admin", %{admin: admin, subject1: subject1, subje
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{admin.nickname} made @#{subject2.nickname} unfollow @#{subject1.nickname}" "@#{admin.nickname} made @#{subject2.nickname} unfollow @#{subject1.nickname}"
end end
@ -100,8 +99,7 @@ test "logging user tagged by admin", %{admin: admin, subject1: subject1, subject
tags = ["foo", "bar"] |> Enum.join(", ") tags = ["foo", "bar"] |> Enum.join(", ")
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] == "@#{admin.nickname} added tags: #{tags} to users: #{users}"
"@#{admin.nickname} added tags: #{tags} to users: #{users}"
end end
test "logging user untagged by admin", %{admin: admin, subject1: subject1, subject2: subject2} do test "logging user untagged by admin", %{admin: admin, subject1: subject1, subject2: subject2} do
@ -122,7 +120,7 @@ test "logging user untagged by admin", %{admin: admin, subject1: subject1, subje
tags = ["foo", "bar"] |> Enum.join(", ") tags = ["foo", "bar"] |> Enum.join(", ")
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{admin.nickname} removed tags: #{tags} from users: #{users}" "@#{admin.nickname} removed tags: #{tags} from users: #{users}"
end end
@ -137,8 +135,7 @@ test "logging user grant by moderator", %{moderator: moderator, subject1: subjec
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] == "@#{moderator.nickname} made @#{subject1.nickname} moderator"
"@#{moderator.nickname} made @#{subject1.nickname} moderator"
end end
test "logging user revoke by moderator", %{moderator: moderator, subject1: subject1} do test "logging user revoke by moderator", %{moderator: moderator, subject1: subject1} do
@ -152,7 +149,7 @@ test "logging user revoke by moderator", %{moderator: moderator, subject1: subje
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} revoked moderator role from @#{subject1.nickname}" "@#{moderator.nickname} revoked moderator role from @#{subject1.nickname}"
end end
@ -166,7 +163,7 @@ test "logging relay follow", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} followed relay: https://example.org/relay" "@#{moderator.nickname} followed relay: https://example.org/relay"
end end
@ -180,7 +177,7 @@ test "logging relay unfollow", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} unfollowed relay: https://example.org/relay" "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
end end
@ -202,7 +199,7 @@ test "logging report update", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} updated report ##{report.id} with 'resolved' state" "@#{moderator.nickname} updated report ##{report.id} with 'resolved' state"
end end
@ -224,7 +221,7 @@ test "logging report response", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} responded with 'look at this' to report ##{report.id}" "@#{moderator.nickname} responded with 'look at this' to report ##{report.id}"
end end
@ -242,7 +239,7 @@ test "logging status sensitivity update", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true'" "@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true'"
end end
@ -260,7 +257,7 @@ test "logging status visibility update", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set visibility: 'private'" "@#{moderator.nickname} updated status ##{note.id}, set visibility: 'private'"
end end
@ -278,7 +275,7 @@ test "logging status sensitivity & visibility update", %{moderator: moderator} d
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] ==
"@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true', visibility: 'private'" "@#{moderator.nickname} updated status ##{note.id}, set sensitive: 'true', visibility: 'private'"
end end
@ -294,8 +291,7 @@ test "logging status deletion", %{moderator: moderator} do
log = Repo.one(ModerationLog) log = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log) == assert log.data["message"] == "@#{moderator.nickname} deleted status ##{note.id}"
"@#{moderator.nickname} deleted status ##{note.id}"
end end
end end
end end

View file

@ -2251,8 +2251,9 @@ test "returns private statuses with godmode on", %{conn: conn, user: user} do
describe "GET /api/pleroma/admin/moderation_log" do describe "GET /api/pleroma/admin/moderation_log" do
setup %{conn: conn} do setup %{conn: conn} do
admin = insert(:user, info: %{is_admin: true}) admin = insert(:user, info: %{is_admin: true})
moderator = insert(:user, info: %{is_moderator: true})
%{conn: assign(conn, :user, admin), admin: admin} %{conn: assign(conn, :user, admin), admin: admin, moderator: moderator}
end end
test "returns the log", %{conn: conn, admin: admin} do test "returns the log", %{conn: conn, admin: admin} do
@ -2394,6 +2395,64 @@ test "filters log by date", %{conn: conn, admin: admin} do
assert first_entry["message"] == assert first_entry["message"] ==
"@#{admin.nickname} unfollowed relay: https://example.org/relay" "@#{admin.nickname} unfollowed relay: https://example.org/relay"
end end
test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => admin.id,
"nickname" => admin.nickname,
"type" => "user"
},
action: "relay_follow",
target: "https://example.org/relay"
}
})
Repo.insert(%ModerationLog{
data: %{
actor: %{
"id" => moderator.id,
"nickname" => moderator.nickname,
"type" => "user"
},
action: "relay_unfollow",
target: "https://example.org/relay"
}
})
conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
response1 = json_response(conn1, 200)
[first_entry] = response1
assert response1 |> length() == 1
assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
end
test "returns log filtered by search", %{conn: conn, moderator: moderator} do
ModerationLog.insert_log(%{
actor: moderator,
action: "relay_follow",
target: "https://example.org/relay"
})
ModerationLog.insert_log(%{
actor: moderator,
action: "relay_unfollow",
target: "https://example.org/relay"
})
conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
response1 = json_response(conn1, 200)
[first_entry] = response1
assert response1 |> length() == 1
assert get_in(first_entry, ["data", "message"]) ==
"@#{moderator.nickname} unfollowed relay: https://example.org/relay"
end
end end
end end