mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
admin reports
This commit is contained in:
parent
3e2dfb3fd9
commit
181931cf67
12 changed files with 321 additions and 1 deletions
13
lib/philomena/reports/attribution.ex
Normal file
13
lib/philomena/reports/attribution.ex
Normal 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
|
26
lib/philomena/reports/query.ex
Normal file
26
lib/philomena/reports/query.ex
Normal 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
|
|
@ -13,6 +13,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
alias Philomena.DnpEntries.DnpEntry
|
alias Philomena.DnpEntries.DnpEntry
|
||||||
alias Philomena.UserLinks.UserLink
|
alias Philomena.UserLinks.UserLink
|
||||||
alias Philomena.Tags.Tag
|
alias Philomena.Tags.Tag
|
||||||
|
alias Philomena.Reports.Report
|
||||||
|
|
||||||
# Admins can do anything
|
# Admins can do anything
|
||||||
def can?(%User{role: "admin"}, _action, _model), do: true
|
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
|
# View IP addresses and fingerprints
|
||||||
def can?(%User{role: "moderator"}, :show, :ip_address), do: true
|
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...
|
# Assistants can...
|
||||||
#
|
#
|
||||||
|
|
|
@ -34,6 +34,7 @@ defmodule Philomena.Users.User do
|
||||||
has_many :awards, Badges.Award
|
has_many :awards, Badges.Award
|
||||||
has_many :unread_notifications, UnreadNotification
|
has_many :unread_notifications, UnreadNotification
|
||||||
has_many :notifications, through: [:unread_notifications, :notification]
|
has_many :notifications, through: [:unread_notifications, :notification]
|
||||||
|
has_many :linked_tags, through: [:verified_links, :tag]
|
||||||
has_one :commission, Commission
|
has_one :commission, Commission
|
||||||
many_to_many :roles, Role, join_through: "users_roles"
|
many_to_many :roles, Role, join_through: "users_roles"
|
||||||
|
|
||||||
|
|
83
lib/philomena_web/controllers/admin/report_controller.ex
Normal file
83
lib/philomena_web/controllers/admin/report_controller.ex
Normal 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
|
|
@ -156,6 +156,10 @@ defmodule PhilomenaWeb.Router do
|
||||||
|
|
||||||
resources "/ip_profiles", IpProfileController, only: [:show]
|
resources "/ip_profiles", IpProfileController, only: [:show]
|
||||||
resources "/fingerprint_profiles", FingerprintProfileController, only: [:show]
|
resources "/fingerprint_profiles", FingerprintProfileController, only: [:show]
|
||||||
|
|
||||||
|
scope "/admin", Admin, as: :admin do
|
||||||
|
resources "/reports", ReportController, only: [:index, :show]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", PhilomenaWeb do
|
scope "/", PhilomenaWeb do
|
||||||
|
|
51
lib/philomena_web/templates/admin/report/_reports.html.slime
Normal file
51
lib/philomena_web/templates/admin/report/_reports.html.slime
Normal 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
|
||||||
|
' •
|
||||||
|
= link_to 'Send PM', new_conversation_path(title: "Your Report of #{reported_thing(report.reportable)}", recipient: report.user.name)
|
||||||
|
- if report.admin != current_user
|
||||||
|
' •
|
||||||
|
- 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
|
||||||
|
' •
|
||||||
|
= link_to 'Release', admin_report_claim_path(report), method: :delete
|
||||||
|
' •
|
||||||
|
= link_to t('close'), admin_report_close_path(report), data: { confirm: t('are_you_sure') }, method: :post
|
35
lib/philomena_web/templates/admin/report/index.html.slime
Normal file
35
lib/philomena_web/templates/admin/report/index.html.slime
Normal 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.
|
43
lib/philomena_web/templates/admin/report/show.html.slime
Normal file
43
lib/philomena_web/templates/admin/report/show.html.slime
Normal 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"
|
|
@ -52,7 +52,7 @@
|
||||||
span.header__counter__admin
|
span.header__counter__admin
|
||||||
= @duplicate_report_count
|
= @duplicate_report_count
|
||||||
= if @report_count do
|
= 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
|
' R
|
||||||
span.header__counter__admin
|
span.header__counter__admin
|
||||||
= @report_count
|
= @report_count
|
||||||
|
|
23
lib/philomena_web/views/admin/report_view.ex
Normal file
23
lib/philomena_web/views/admin/report_view.ex
Normal 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
|
|
@ -50,6 +50,41 @@ defmodule PhilomenaWeb.ProfileView do
|
||||||
Enum.map_join(tags, " || ", & &1.name)
|
Enum.map_join(tags, " || ", & &1.name)
|
||||||
end
|
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, 0), do: 0
|
||||||
defp zero_div(num, den), do: div(num, den)
|
defp zero_div(num, den), do: div(num, den)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue