image options area

This commit is contained in:
byte[] 2019-12-03 23:14:56 -05:00
parent bd6614452f
commit 600d37c7c0
21 changed files with 514 additions and 9 deletions

View file

@ -18,10 +18,12 @@ defmodule Philomena.Polymorphic do
} }
@preloads %{ @preloads %{
"Comment" => [:user], "Comment" => [:user, :image],
"Commission" => [:user],
"Conversation" => [:from, :to],
"Gallery" => [:creator], "Gallery" => [:creator],
"Image" => [:user, :tags], "Image" => [:user, :tags],
"Post" => [:user], "Post" => [:user, topic: :forum],
"Topic" => [:forum, :user] "Topic" => [:forum, :user]
} }

View file

@ -7,6 +7,7 @@ defmodule Philomena.Reports do
alias Philomena.Repo alias Philomena.Repo
alias Philomena.Reports.Report alias Philomena.Reports.Report
alias Philomena.Polymorphic
@doc """ @doc """
Returns the list of reports. Returns the list of reports.
@ -49,9 +50,9 @@ defmodule Philomena.Reports do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def create_report(attrs \\ %{}) do def create_report(reportable_id, reportable_type, attribution, attrs \\ %{}) do
%Report{} %Report{reportable_id: reportable_id, reportable_type: reportable_type}
|> Report.changeset(attrs) |> Report.creation_changeset(attrs, attribution)
|> Repo.insert() |> Repo.insert()
end end
@ -101,4 +102,18 @@ defmodule Philomena.Reports do
def change_report(%Report{} = report) do def change_report(%Report{} = report) do
Report.changeset(report, %{}) Report.changeset(report, %{})
end end
def reindex_report(%Report{} = report) do
spawn fn ->
Report
|> where(id: ^report.id)
|> preload([:user, :admin])
|> Repo.all()
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
|> hd()
|> Report.index_document()
end
report
end
end end

View file

@ -0,0 +1,57 @@
defmodule Philomena.Reports.Elasticsearch do
def mapping do
%{
settings: %{
index: %{
number_of_shards: 5,
max_result_window: 10_000_000
}
},
mappings: %{
report: %{
_all: %{enabled: false},
dynamic: false,
properties: %{
id: %{type: "integer"},
image_id: %{type: "integer"},
created_at: %{type: "date"},
ip: %{type: "ip"},
fingerprint: %{type: "keyword"},
state: %{type: "keyword"},
user: %{type: "keyword"},
user_id: %{type: "keyword"},
admin: %{type: "keyword"},
admin_id: %{type: "keyword"},
reportable_type: %{type: "keyword"},
reportable_id: %{type: "keyword"},
open: %{type: "boolean"},
reason: %{type: "text", analyzer: "snowball"}
}
}
}
}
end
def as_json(report) do
%{
id: report.id,
image_id: image_id(report),
created_at: report.created_at,
ip: report.ip |> to_string(),
state: report.state,
user: if(report.user, do: String.downcase(report.user.name)),
user_id: report.user_id,
admin: if(report.admin, do: String.downcase(report.admin.name)),
admin_id: report.admin_id,
reportable_type: report.reportable_type,
reportable_id: report.reportable_id,
fingerprint: report.fingerprint,
open: report.open,
reason: report.reason
}
end
defp image_id(%{reportable_type: "Image", reportable_id: image_id}), do: image_id
defp image_id(%{reportable_type: "Comment", reportable: %{image_id: image_id}}), do: image_id
defp image_id(_report), do: nil
end

View file

@ -2,6 +2,11 @@ defmodule Philomena.Reports.Report do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
use Philomena.Elasticsearch,
definition: Philomena.Reports.Elasticsearch,
index_name: "reports",
doc_type: "report"
alias Philomena.Users.User alias Philomena.Users.User
schema "reports" do schema "reports" do
@ -21,6 +26,7 @@ defmodule Philomena.Reports.Report do
field :reportable_type, :string field :reportable_type, :string
field :reportable, :any, virtual: true field :reportable, :any, virtual: true
field :category, :string, virtual: true
timestamps(inserted_at: :created_at) timestamps(inserted_at: :created_at)
end end
@ -31,4 +37,25 @@ defmodule Philomena.Reports.Report do
|> cast(attrs, []) |> cast(attrs, [])
|> validate_required([]) |> validate_required([])
end end
@doc false
def creation_changeset(report, attrs, attribution) do
report
|> cast(attrs, [:category, :reason])
|> merge_category()
|> change(attribution)
|> validate_required([:reportable_id, :reportable_type, :category, :reason, :ip, :fingerprint, :user_agent])
end
defp merge_category(changeset) do
reason = get_field(changeset, :reason)
category = get_field(changeset, :category)
changeset
|> change(reason: joiner(category, reason))
end
defp joiner(category, ""), do: category
defp joiner(category, nil), do: category
defp joiner(category, reason), do: category <> ": " <> reason
end end

View file

@ -0,0 +1,11 @@
defmodule PhilomenaWeb.Image.FavoritesController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [faves: :user]
def index(conn, _params) do
render(conn, "index.html", layout: false, image: conn.assigns.image)
end
end

View file

@ -0,0 +1,34 @@
defmodule PhilomenaWeb.Image.ReportController do
use PhilomenaWeb, :controller
alias PhilomenaWeb.ReportController
alias PhilomenaWeb.ReportView
alias Philomena.Images.Image
alias Philomena.Reports.Report
alias Philomena.Reports
plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CaptchaPlug when action in [:create]
plug PhilomenaWeb.CanaryMapPlug, new: :show, create: :show
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
def new(conn, _params) do
image = conn.assigns.image
action = Routes.image_report_path(conn, :create, image)
changeset =
%Report{reportable_type: "Image", reportable_id: image.id}
|> Reports.change_report()
conn
|> put_view(ReportView)
|> render("new.html", reportable: image, changeset: changeset, action: action)
end
def create(conn, params) do
image = conn.assigns.image
action = Routes.image_report_path(conn, :create, image)
ReportController.create(conn, action, image, "Image", params)
end
end

View file

@ -0,0 +1,27 @@
defmodule PhilomenaWeb.Image.ReportingController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.DuplicateReports.DuplicateReport
alias Philomena.DuplicateReports
alias Philomena.Repo
import Ecto.Query
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
def show(conn, _params) do
image = conn.assigns.image
dupe_reports =
DuplicateReport
|> preload([image: :tags, duplicate_of_image: :tags])
|> where([d], d.image_id == ^image.id or d.duplicate_of_image_id == ^image.id)
|> Repo.all()
changeset =
%DuplicateReport{}
|> DuplicateReports.change_duplicate_report()
render(conn, "show.html", layout: false, image: image, dupe_reports: dupe_reports, changeset: changeset)
end
end

View file

@ -0,0 +1,89 @@
defmodule PhilomenaWeb.ReportController do
use PhilomenaWeb, :controller
alias Philomena.Polymorphic
alias Philomena.Reports.Report
alias Philomena.Reports
alias Philomena.Repo
import Ecto.Query
def index(conn, _params) do
user = conn.assigns.current_user
reports =
Report
|> where(user_id: ^user.id)
|> Repo.paginate(conn.assigns.scrivener)
polymorphic =
reports
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
reports =
%{reports | entries: polymorphic}
render(conn, "index.html", reports: reports)
end
# Make sure that you load the resource in your controller:
#
# plug PhilomenaWeb.FilterBannedUsersPlug
# plug PhilomenaWeb.UserAttributionPlug
# plug PhilomenaWeb.CaptchaPlug when action in [:create]
# plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def create(conn, action, reportable, reportable_type, %{"report" => report_params}) do
attribution = conn.assigns.attributes
case too_many_reports?(conn) do
true ->
conn
|> put_flash(:error, "You may not have more than 5 open reports at a time. Did you read the reporting tips?")
|> redirect(to: "/")
_falsy ->
case Reports.create_report(reportable.id, reportable_type, attribution, report_params) do
{:ok, report} ->
Reports.reindex_report(report)
conn
|> put_flash(:info, "Your report has been received and will be checked by staff shortly.")
|> redirect(to: "/")
{:error, changeset} ->
# Note that we are depending on the controller that called
# us to have set up the view already (Phoenix does this)
conn
|> render("new.html", reportable: reportable, changeset: changeset, action: action)
end
end
end
defp too_many_reports?(conn) do
user = conn.assigns.current_user
too_many_reports_user?(user) or too_many_reports_ip?(conn)
end
defp too_many_reports_user?(nil), do: false
defp too_many_reports_user?(user) do
reports_open =
Report
|> where(user_id: ^user.id)
|> where([r], r.state in ["open", "in_progress"])
|> Repo.aggregate(:count, :id)
reports_open >= 5
end
defp too_many_reports_ip?(conn) do
attribution = conn.assigns.attributes
reports_open =
Report
|> where(ip: ^attribution[:ip])
|> where([r], r.state in ["open", "in_progress"])
|> Repo.aggregate(:count, :id)
reports_open >= 5
end
end

View file

@ -114,6 +114,8 @@ defmodule PhilomenaWeb.Router do
scope "/filters", Filter, as: :filter do scope "/filters", Filter, as: :filter do
resources "/spoiler_type", SpoilerTypeController, only: [:update], singleton: true resources "/spoiler_type", SpoilerTypeController, only: [:update], singleton: true
end end
resources "/reports", ReportController, only: [:index]
end end
scope "/", PhilomenaWeb do scope "/", PhilomenaWeb do
@ -134,6 +136,9 @@ defmodule PhilomenaWeb.Router do
resources "/source_changes", Image.SourceChangeController, only: [:index] resources "/source_changes", Image.SourceChangeController, only: [:index]
resources "/description", Image.DescriptionController, only: [:update], singleton: true resources "/description", Image.DescriptionController, only: [:update], singleton: true
resources "/navigate", Image.NavigateController, only: [:index] resources "/navigate", Image.NavigateController, only: [:index]
resources "/reports", Image.ReportController, only: [:new, :create]
resources "/reporting", Image.ReportingController, only: [:show], singleton: true
resources "/favorites", Image.FavoritesController, only: [:index]
end end
scope "/tags", Tag, as: :tag do scope "/tags", Tag, as: :tag do
resources "/autocomplete", AutocompleteController, only: [:show], singleton: true resources "/autocomplete", AutocompleteController, only: [:show], singleton: true
@ -170,7 +175,7 @@ defmodule PhilomenaWeb.Router do
resources "/stats", StatController, only: [:index] resources "/stats", StatController, only: [:index]
resources "/channels", ChannelController, only: [:index, :show] resources "/channels", ChannelController, only: [:index, :show]
resources "/settings", SettingController, only: [:edit, :update], singleton: true resources "/settings", SettingController, only: [:edit, :update], singleton: true
resources "/duplicate_reports", DuplicateReportController, only: [:index, :show] resources "/duplicate_reports", DuplicateReportController, only: [:index, :show, :create]
get "/:id", ImageController, :show get "/:id", ImageController, :show
# get "/:forum_id", ForumController, :show # impossible to do without constraints # get "/:forum_id", ForumController, :show # impossible to do without constraints

View file

@ -0,0 +1,8 @@
= form_for @changeset, Routes.duplicate_report_path(@conn, :create), fn f ->
= hidden_input f, :image_id, value: @image.id
.field
' Delete this image and redirect to
= number_input f, :duplicate_of_image_id, class: "input", min: 0, placeholder: "image number.", required: true
= text_input f, :reason, class: "input input--separate-left", placeholder: "Explanation"
.actions
= submit "Report Duplicate", class: "button"

View file

@ -0,0 +1,55 @@
#image_options_area
.block__header.block__header--js-tabbed
a href="#" data-click-tab="reporting" data-load-tab=Routes.image_reporting_path(@conn, :show, @image)
i.fa.fa-exclamation-triangle>
| Report
a href="#" data-click-tab="sharing"
i.fa.fa-share>
| Share
a href="#" data-click-tab="favoriters" data-load-tab=Routes.image_favorites_path(@conn, :index, @image)
i.fa.fa-star>
| List favoriters
.block__tab.hidden data-tab="favoriters"
p Loading...
.block__tab.hidden data-tab="reporting"
p Loading...
.block__tab.hidden data-tab="sharing"
#embed_options
- source_link = if @image.source_url in [nil, ""], do: " (Original source unknown at time of posting)", else: " - [url=#{@image.source_url}]Original source[/url]"
h5 Derpibooru
p
strong> Small thumbnail
input.input#embed_small_thumbnail_tag type="text" value=">>#{@image.id}s" cols="10" readonly="readonly"
a< href="#" data-click-copy="#embed_small_thumbnail_tag"
i.fa.fa-clipboard>
| Copy
p
strong> Thumbnail
input.input#embed_thumbnail_tag type="text" value=">>#{@image.id}t" cols="10" readonly="readonly"
a< href="#" data-click-copy="#embed_thumbnail_tag"
i.fa.fa-clipboard>
| Copy
p
strong> Preview
input.input#embed_preview_tag type="text" value=">>#{@image.id}p" cols="10" readonly="readonly"
a< href="#" data-click-copy="#embed_preview_tag"
i.fa.fa-clipboard>
| Copy
h5 BBCode
p
strong> Full size BBcode
a href="#" data-click-copy="#bbcode_embed_full_tag"
i.fa.fa-clipboard>
| Copy
br
textarea.input.input--wide.input--separate-top#bbcode_embed_full_tag rows="2" cols="100" readonly="readonly"
= "[img]#{thumb_url(@image, false, :full)}[/img]\n[url=#{Routes.image_url(@conn, :show, @image)}]View on Derpibooru[/url]#{source_link}"
p
strong> Thumbnailed BBcode
a href="#" data-click-copy="#bbcode_embed_thumbnail_tag"
i.fa.fa-clipboard>
| Copy
br
textarea.input.input--wide.input--separate-top#bbcode_embed_thumbnail_tag rows="2" cols="100" readonly="readonly"
= "[img]#{thumb_url(@image, false, :medium)}[/img]\n[url=#{Routes.image_url(@conn, :show, @image)}]View on Derpibooru[/url]#{source_link}"

View file

@ -0,0 +1,4 @@
h5 Favorited by
= for fave <- Enum.sort_by(@image.faves, & &1.user.name) do
=> link fave.user.name, to: Routes.profile_path(@conn, :show, fave.user), class: "interaction-user-list-item"

View file

@ -0,0 +1,23 @@
a href=Routes.image_report_path(@conn, :new, @image)
button.button.button--link
i.fa.fa-exclamation-triangle>
' General reporting
.report-duplicate
- checked = Enum.any?(@dupe_reports, & &1.state == "open")
input.toggle-box id="image-dedupe" type="checkbox" checked=checked
label for="image-dedupe" Updating/merging
.toggle-box-container
.toggle-box-container__content
= if @conn.assigns.current_user do
= render PhilomenaWeb.DuplicateReportView, "_form.html", image: @image, conn: @conn, changeset: @changeset
- else
p
' You must
a> href=Routes.pow_session_path(@conn, :new) log in
' to report duplicate images.
h4 Existing duplicate reports
= render PhilomenaWeb.DuplicateReportView, "_list.html", duplicate_reports: Enum.filter(@dupe_reports, & &1.duplicate_of_image_id == @image.id)
= render PhilomenaWeb.DuplicateReportView, "_list.html", duplicate_reports: Enum.filter(@dupe_reports, & &1.image_id == @image.id)

View file

@ -11,6 +11,7 @@
= render PhilomenaWeb.ImageView, "_tags.html", image: @image, changeset: @image_changeset, conn: @conn = render PhilomenaWeb.ImageView, "_tags.html", image: @image, changeset: @image_changeset, conn: @conn
= render PhilomenaWeb.ImageView, "_source.html", image: @image, changeset: @image_changeset, conn: @conn = render PhilomenaWeb.ImageView, "_source.html", image: @image, changeset: @image_changeset, conn: @conn
= render PhilomenaWeb.ImageView, "_options.html", image: @image, conn: @conn
h4 Comments h4 Comments
= cond do = cond do

View file

@ -27,7 +27,7 @@ meta name="robots" content="noindex, nofollow"
meta name="og:video" content=ImageView.thumb_url(image, false, :full) meta name="og:video" content=ImageView.thumb_url(image, false, :full)
- image.image_mime_type == "image/svg+xml" and !filtered -> - image.image_mime_type == "image/svg+xml" and !filtered ->
meta name="og:type" content="video.other" meta name="og:type" content="website"
meta name="og:image" content=ImageView.thumb_url(image, false, :rendered) meta name="og:image" content=ImageView.thumb_url(image, false, :rendered)
- !filtered -> - !filtered ->

View file

@ -0,0 +1,23 @@
h1 Your Reports
.block
.block__header
span.block__header__title Reports
- route = fn p -> Routes.report_path(@conn, :index, p) end
= render PhilomenaWeb.PaginationView, "_pagination.html", page: @reports, route: route, conn: @conn
.block__content
table.table
thead
tr
th State
th Reported Thing
th Reason
th Opened
tbody
= for r <- @reports do
tr
td class=report_row_class(r)
= pretty_state(r)
td = link_to_reported_thing(@conn, r.reportable)
td = r.reason
td = pretty_time(r.created_at)

View file

@ -0,0 +1,53 @@
h2 Submit a report
p
strong
= link_to_reported_thing(@conn, @reportable)
.image-other
.dnp-warning
h3 Reporting Tips
ul
li
' Make sure to report for the correct
= link "rule", to: "/pages/rules"
' .
li One report per problem (mention all things affected, please).
li
strong Do not report someone just because they disagree with you. Abusing the report system is a bannable offense.
br
= if image?(@changeset) do
.dnp-warning
h3 Takedown Policy
p
strong> Only an owner of an image's rights (normally the artist) can request a takedown.
' If you're the artist, you'll
strong> need
' a verified user link.
p
' For more information, please read the
= link "takedown policy", to: "/pages/takedowns"
' .
br
= if conversation?(@changeset) do
.dnp-warning
h3 Privacy note
p
' The whole conversation will be readable by site staff.
br
= form_for @changeset, @action, fn f ->
.field
= select f, :category, report_categories(), class: "input"
.field
= textarea f, :reason, class: "input input--wide", placeholder: "Provide anything else we should know here."
= if !@conn.assigns.current_user do
.field
= checkbox f, :captcha, class: "js-captcha", value: 0
= label f, :captcha, "I am not a robot!"
p
' This helps stop bot spam; log in if you don't want to deal with captchas.
= submit "Send Report", class: "button"

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Image.FavoritesView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Image.ReportingView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,65 @@
defmodule PhilomenaWeb.ReportView do
use PhilomenaWeb, :view
alias Philomena.Images.Image
alias Philomena.Comments.Comment
alias Philomena.Commissions.Commission
alias Philomena.Conversations.Conversation
alias Philomena.Posts.Post
alias Philomena.Users.User
import Ecto.Changeset
def report_categories do
[
"Rule #0: Namecalling, trolling, discrimination": "Rule #0",
"Rule #1: DNP, content theft, pay content, trace/bad edit": "Rule #1",
"Rule #2: Bad tagging/sourcing": "Rule #2",
"Rule #3: Image not MLP-related/obligatory pony": "Rule #3",
"Rule #4: Whining about filterable content": "Rule #4",
"Rule #5: Underage+human/anthro-looking porn": "Rule #5",
"Rule #6: Spam, off-topic, or general site abuse": "Rule #6",
"Rule #7: Above topic rating (NOT swear words)": "Rule #7",
"Rule #8: Privacy violation": "Rule #8",
"Rule #9: Commissions": "Rule #9",
"Rule #n: Spirit of the rules": "Rule #n",
"Other (please explain)": "Other",
"Takedown request": "Takedown request"
]
end
def image?(changeset), do: get_field(changeset, :reportable_type) == "Image"
def conversation?(changeset), do: get_field(changeset, :reportable_type) == "Conversation"
def report_row_class(%{state: "closed"}), do: "success"
def report_row_class(%{state: "in_progress"}), do: "warning"
def report_row_class(_report), do: "danger"
def pretty_state(%{state: "closed"}), do: "Closed"
def pretty_state(%{state: "in_progress"}), do: "In progress"
def pretty_state(%{state: "claimed"}), do: "Claimed"
def pretty_state(_report), do: "Open"
def link_to_reported_thing(conn, %Image{} = r) when not is_nil(r),
do: link "Image >>#{r.id}", to: Routes.image_path(conn, :show, r)
def link_to_reported_thing(conn, %Comment{} = r) when not is_nil(r),
do: link "Comment on image >>#{r.image.id}", to: Routes.image_path(conn, :show, r.image)
def link_to_reported_thing(conn, %Conversation{} = r) when not is_nil(r),
do: link "Conversation between #{r.from.name} and #{r.to.name}", to: Routes.conversation_path(conn, :show, r)
def link_to_reported_thing(conn, %Commission{} = r) when not is_nil(r),
do: link "#{r.user}'s commission page", to: Routes.commission_path(conn, :show, r)
def link_to_reported_thing(conn, %Post{} = r) when not is_nil(r),
do: link "Post in #{r.topic.title}", to: Routes.forum_topic_path(conn, :show, r.topic.forum, r.topic, post_id: r.id) <> "#post_#{r.id}"
def link_to_reported_thing(conn, %User{} = r) when not is_nil(r),
do: link "User '#{r.name}'", to: Routes.profile_path(conn, :show, r)
def link_to_reported_thing(_conn, report) do
IO.inspect report
"Reported item permanently destroyed."
end
end

View file

@ -10,12 +10,12 @@
# We recommend using the bang functions (`insert!`, `update!` # We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong. # and so on) as they will fail if something goes wrong.
alias Philomena.{Repo, Comments.Comment, Filters.Filter, Forums.Forum, Galleries.Gallery, Posts.Post, Images.Image, Tags.Tag, Users.User} alias Philomena.{Repo, Comments.Comment, Filters.Filter, Forums.Forum, Galleries.Gallery, Posts.Post, Images.Image, Reports.Report, Tags.Tag, Users.User}
alias Philomena.Tags alias Philomena.Tags
import Ecto.Query import Ecto.Query
IO.puts "---- Creating Elasticsearch indices" IO.puts "---- Creating Elasticsearch indices"
for model <- [Image, Comment, Gallery, Tag, Post] do # Report for model <- [Image, Comment, Gallery, Tag, Post, Report] do # Report
model.delete_index! model.delete_index!
model.create_index! model.create_index!
end end