admin reports

This commit is contained in:
byte[] 2019-12-08 12:45:37 -05:00
parent 3e2dfb3fd9
commit 181931cf67
12 changed files with 321 additions and 1 deletions

View file

@ -0,0 +1,13 @@
defimpl Philomena.Attribution, for: Philomena.Reports.Report do
def object_identifier(report) do
to_string(report.id)
end
def best_user_identifier(report) do
to_string(report.user_id || report.fingerprint || report.ip)
end
def anonymous?(report) do
false
end
end

View file

@ -0,0 +1,26 @@
defmodule Philomena.Reports.Query do
alias Search.Parser
int_fields = ~W(id image_id)
date_fields = ~W(created_at)
literal_fields = ~W(state user user_id admin admin_id reportable_type reportable_id fingerprint)
ip_fields = ~W(ip)
bool_fields = ~W(open)
ngram_fields = ~W(reason)
custom_fields = ~W(author user_id)
default_field = "reason"
@parser Parser.parser(
int_fields: int_fields,
date_fields: date_fields,
literal_fields: literal_fields,
ip_fields: ip_fields,
bool_fields: bool_fields,
ngram_fields: ngram_fields,
default_field: default_field
)
def compile(query_string) do
Parser.parse(@parser, query_string || "", %{})
end
end

View file

@ -13,6 +13,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
alias Philomena.DnpEntries.DnpEntry
alias Philomena.UserLinks.UserLink
alias Philomena.Tags.Tag
alias Philomena.Reports.Report
# Admins can do anything
def can?(%User{role: "admin"}, _action, _model), do: true
@ -41,6 +42,11 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# View IP addresses and fingerprints
def can?(%User{role: "moderator"}, :show, :ip_address), do: true
# View reports
def can?(%User{role: "moderator"}, :index, Report), do: true
def can?(%User{role: "moderator"}, :show, %Report{}), do: true
def can?(%User{role: "moderator"}, :edit, %Report{}), do: true
#
# Assistants can...
#

View file

@ -34,6 +34,7 @@ defmodule Philomena.Users.User do
has_many :awards, Badges.Award
has_many :unread_notifications, UnreadNotification
has_many :notifications, through: [:unread_notifications, :notification]
has_many :linked_tags, through: [:verified_links, :tag]
has_one :commission, Commission
many_to_many :roles, Role, join_through: "users_roles"

View file

@ -0,0 +1,83 @@
defmodule PhilomenaWeb.Admin.ReportController do
use PhilomenaWeb, :controller
alias Philomena.Textile.Renderer
alias Philomena.Reports.Report
alias Philomena.Reports.Query
alias Philomena.Polymorphic
alias Philomena.Repo
import Ecto.Query
plug :load_and_authorize_resource, model: Report, preload: [:admin, user: [:linked_tags, awards: :badge]]
def index(conn, %{"rq" => query_string}) do
{:ok, query} = Query.compile(query_string)
reports = load_reports(conn, query)
render(conn, "index.html", layout_class: "layout--wide", reports: reports, my_reports: [])
end
def index(conn, _params) do
user = conn.assigns.current_user
query =
%{
bool: %{
should: [
%{term: %{open: false}},
%{
bool: %{
must: %{term: %{open: true}},
must_not: %{term: %{admin_id: user.id}}
}
}
]
}
}
reports = load_reports(conn, query)
my_reports =
Report
|> where(open: true, admin_id: ^user.id)
|> preload([:admin, user: :linked_tags])
|> order_by(desc: :created_at)
|> Repo.all()
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
render(conn, "index.html", layout_class: "layout--wide", reports: reports, my_reports: my_reports)
end
def show(conn, _params) do
[report] = Polymorphic.load_polymorphic([conn.assigns.report], reportable: [reportable_id: :reportable_type])
body = Renderer.render_one(%{body: report.reason}, conn)
render(conn, "show.html", report: report, body: body)
end
defp load_reports(conn, query) do
reports =
Report.search_records(
%{
query: query,
sort: sorts()
},
conn.assigns.pagination,
Report |> preload([:admin, user: :linked_tags])
)
entries =
Polymorphic.load_polymorphic(reports, reportable: [reportable_id: :reportable_type])
%{reports | entries: entries}
end
defp sorts do
[
%{open: :desc},
%{state: :desc},
%{created_at: :desc}
]
end
end

View file

@ -156,6 +156,10 @@ defmodule PhilomenaWeb.Router do
resources "/ip_profiles", IpProfileController, only: [:show]
resources "/fingerprint_profiles", FingerprintProfileController, only: [:show]
scope "/admin", Admin, as: :admin do
resources "/reports", ReportController, only: [:index, :show]
end
end
scope "/", PhilomenaWeb do

View file

@ -0,0 +1,51 @@
table.table
thead
tr
th Thing
th Reason
th User
th.hide-mobile Opened
th State
th Options
tbody
= for report <- @reports do
tr
td
= link_to_reported_thing @conn, report.reportable
td
span title=report.reason
= truncate(report.reason)
td
= if report.user do
= link report.user.name, to: Routes.profile_path(@conn, :show, report.user)
- else
em>
= truncated_ip_link(@conn, report.ip)
= link_to_fingerprint(@conn, report.fingerprint)
= if not is_nil(report.user) and Enum.any?(report.user.linked_tags) do
= render PhilomenaWeb.TagView, "_tag_list.html", tags: ordered_tags(report.user.linked_tags), conn: @conn
td.hide-mobile
= pretty_time report.created_at
td class=report_row_class(report)
=> pretty_state(report)
= user_abbrv report.admin
td
= link "Show", to: Routes.admin_report_path(@conn, :show, report)
/- if report.open
- if report.user
' &bull;
= link_to 'Send PM', new_conversation_path(title: "Your Report of #{reported_thing(report.reportable)}", recipient: report.user.name)
- if report.admin != current_user
' &bull;
- if report.admin.present?
= link_to 'Claim', admin_report_claim_path(report), method: :post, data: { confirm: t('admin.reports.change_owner') }
- else
= link_to 'Claim', admin_report_claim_path(report), method: :post
- if report.admin == current_user
' &bull;
= link_to 'Release', admin_report_claim_path(report), method: :delete
' &bull;
= link_to t('close'), admin_report_close_path(report), data: { confirm: t('are_you_sure') }, method: :post

View file

@ -0,0 +1,35 @@
- route = fn p -> Routes.admin_report_path(@conn, :index, p) end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @reports
h1 Reports
= if Enum.any?(@my_reports) do
.block
.block__header
span.block__header__title Your Reports
.block__content
= render PhilomenaWeb.Admin.ReportView, "_reports.html", reports: @my_reports, conn: @conn
.block
.block__header
span.block__header__title All Reports
= pagination
.block__content
= if Enum.any?(@reports) do
= render PhilomenaWeb.Admin.ReportView, "_reports.html", reports: @reports, conn: @conn
- else
p We couldn't find any reports for you, sorry!
.block__header.block__header--light
= pagination
= form_for :report, Routes.admin_report_path(@conn, :index), [method: "get", class: "hform"], fn f ->
.field
= text_input f, :rq, name: :rq, value: @conn.params["rq"], class: "input hform__text", placeholder: "Search reports", autocapitalize: "none"
= submit "Search", class: "hform__button button"
.field
label for="rq"
' Searchable fields: id, created_at, reason, state, open, user, user_id, admin, admin_id, ip, fingerprint, reportable_type, reportable_id, image_id
br
' Report reason is used if you don't specify a field.

View file

@ -0,0 +1,43 @@
h1 Showing Report
p
= link_to_reported_thing @conn, @report.reportable
article.block.communication
.block__content.flex.flex--no-wrap
.flex__fixed.spacing-right
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @report, conn: @conn
.flex__grow.communication__body
span.communication__body__sender-name = render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: @report, awards: true, conn: @conn
br
= render PhilomenaWeb.UserAttributionView, "_anon_user_title.html", object: @report, conn: @conn
.communication__body__text
==<> @body
.block__content.communication__options
.flex.flex--wrap.flex--spaced-out
div
' Reported
= pretty_time @report.created_at
.flex__right
=> link_to_ip @conn, @report.ip
=> link_to_fingerprint @conn, @report.fingerprint
div
' User-Agent:
code
= @report.user_agent
p
= if @report.user do
=> link "Send PM", to: Routes.conversation_path(@conn, :new, recipient: @report.user.name), class: "button button--link"
= if @report.open do
=> link "Close", to: "#", class: "button", data: [method: "post"]
= if current?(@report.admin, @conn.assigns.current_user) do
=> link "Release", to: "#", class: "button", data: [method: "delete"]
- else
=> link "Claim", to: "#", class: "button", data: [method: "post"]
= link "Back", to: Routes.admin_report_path(@conn, :index), class: "button button-link"

View file

@ -52,7 +52,7 @@
span.header__counter__admin
= @duplicate_report_count
= if @report_count do
= link to: "#", class: "header__link", title: "Reports" do
= link to: Routes.admin_report_path(@conn, :index), class: "header__link", title: "Reports" do
' R
span.header__counter__admin
= @report_count

View file

@ -0,0 +1,23 @@
defmodule PhilomenaWeb.Admin.ReportView do
use PhilomenaWeb, :view
import PhilomenaWeb.ReportView, only: [link_to_reported_thing: 2, report_row_class: 1, pretty_state: 1]
import PhilomenaWeb.ProfileView, only: [user_abbrv: 1, current?: 2]
def truncate(<<string::binary-size(50), _rest::binary>>), do: string <> "..."
def truncate(string), do: string
def truncated_ip_link(conn, ip) do
case to_string(ip) do
<<string::binary-size(25), _rest::binary>> = ip ->
link(string <> "...", to: Routes.ip_profile_path(conn, :show, ip))
ip ->
link(ip, to: Routes.ip_profile_path(conn, :show, ip))
end
end
def ordered_tags(tags) do
Enum.sort_by(tags, & &1.name)
end
end

View file

@ -50,6 +50,41 @@ defmodule PhilomenaWeb.ProfileView do
Enum.map_join(tags, " || ", & &1.name)
end
def user_abbrv(%{name: name}) do
String.upcase(initials_abbrv(name) || uppercase_abbrv(name) || first_letters_abbrv(name))
end
def user_abbrv(_user), do: content_tag(:span, "(n/a)")
defp initials_abbrv(name) do
case String.split(name, " ", parts: 4) do
[<<a1::utf8, _rest::binary>>, <<a2::utf8, _rest::binary>>, <<a3::utf8, _rest::binary>>, <<a4::utf8, _rest::binary>>] ->
<<a1::utf8, a2::utf8, a3::utf8, a4::utf8>>
[<<a1::utf8, _rest::binary>>, <<a2::utf8, _rest::binary>>, <<a3::utf8, _rest::binary>>] ->
<<a1::utf8, a2::utf8, a3::utf8>>
[<<a1::utf8, _rest::binary>>, <<a2::utf8, _rest::binary>>] ->
<<a1::utf8, a2::utf8>>
_ ->
nil
end
end
defp uppercase_abbrv(name) do
case Regex.scan(~r/[A-Z]/, name, capture: :all_but_first) do
[] ->
nil
list ->
Enum.join(list)
end
end
defp first_letters_abbrv(name) do
String.slice(name, 0, 4)
end
defp zero_div(_num, 0), do: 0
defp zero_div(num, den), do: div(num, den)