Skip to content

Commit

Permalink
Merge pull request #5830 from nonamethanks/feat-media-asset-reports
Browse files Browse the repository at this point in the history
Make media assets reportable
  • Loading branch information
nonamethanks authored Nov 4, 2024
2 parents 2239674 b748352 commit f496011
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 63,12 @@
}
}

.moderation-report-notice {
font-weight: bold;
color: var(--moderation-report-text-color);
line-height: 1.25em
}

.media-asset-image {
user-select: none;
width: auto;
Expand Down
3 changes: 3 additions & 0 deletions app/models/media_asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 32,7 @@ class Error < StandardError; end
has_many :uploads, through: :upload_media_assets
has_many :uploaders, through: :uploads, class_name: "User", foreign_key: :uploader_id
has_many :ai_tags
has_many :pending_moderation_reports, -> { pending }, as: :model, class_name: "ModerationReport"
has_many :dtext_links, -> { embedded_media_asset }, foreign_key: :link_target
has_many :embedding_wiki_pages, through: :dtext_links, source: :model, source_type: "WikiPage"

Expand Down Expand Up @@ -439,6 440,7 @@ def expunge!(current_user, log: true)
purge_cached_urls!
update!(status: :expunged)
ModAction.log("expunged media asset ##{id} (md5=#{md5})", :media_asset_expunge, subject: self, user: current_user) if log
pending_moderation_reports.update!(status: :handled, updater: current_user)
end
rescue
update!(status: :failed)
Expand All @@ -451,6 453,7 @@ def trash!(current_user, log: true)
purge_cached_urls!
update!(status: :deleted)
ModAction.log("deleted media asset ##{id} (md5=#{md5})", :media_asset_delete, subject: self, user: current_user) if log
pending_moderation_reports.update!(status: :handled, updater: current_user)
end
rescue
update!(status: :failed)
Expand Down
28 changes: 18 additions & 10 deletions app/models/moderation_report.rb
Original file line number Diff line number Diff line change
@@ -1,7 1,7 @@
# frozen_string_literal: true

class ModerationReport < ApplicationRecord
MODEL_TYPES = %w[Dmail Comment ForumPost]
MODEL_TYPES = %w[Dmail Comment ForumPost MediaAsset]

dtext_attribute :reason, inline: true # defines :dtext_reason

Expand All @@ -14,15 14,16 @@ class ModerationReport < ApplicationRecord
before_validation(on: :create) { model.lock! }
validates :reason, visible_string: true
validates :model_type, inclusion: { in: MODEL_TYPES }
validates :creator, uniqueness: { scope: [:model_type, :model_id], message: "have already reported this message." }, on: :create
validates :creator, uniqueness: { scope: [:model_type, :model_id], message: "have already reported this." }, on: :create

after_create :autoban_reported_user
after_create :autoban_reported_users, unless: -> { model_type == "MediaAsset" }
after_save :notify_reporter
after_save :create_modaction

scope :dmail, -> { where(model_type: "Dmail") }
scope :comment, -> { where(model_type: "Comment") }
scope :forum_post, -> { where(model_type: "ForumPost") }
scope :media_asset, -> { where(model_type: "MediaAsset") }
scope :recent, -> { where("moderation_reports.created_at >= ?", 1.week.ago) }

enum status: {
Expand All @@ -45,9 46,11 @@ def self.visible(user)
end
end

def autoban_reported_user
if SpamDetector.is_spammer?(reported_user)
SpamDetector.ban_spammer!(reported_user)
def autoban_reported_users
reported_users.each do |reported_user|
if SpamDetector.is_spammer?(reported_user)
SpamDetector.ban_spammer!(reported_user)
end
end
end

Expand All @@ -70,19 73,24 @@ def create_modaction
end
end

def reported_user
def reported_users
case model
when Comment, ForumPost
model.creator
[model.creator]
when Dmail
model.from
[model.from]
when MediaAsset
model.uploaders
else
raise NotImplementedError
end
end

def self.received_by(user)
where(model: Comment.where(creator: user)).or(where(model: ForumPost.where(creator: user))).or(where(model: Dmail.received.where(from: user)))
where(model: Comment.where(creator: user))
.or(where(model: ForumPost.where(creator: user)))
.or(where(model: Dmail.received.where(from: user)))
.or(where(model: Upload.where(uploader: user).find_each.flat_map(&:media_assets)))
end

def self.search(params, current_user)
Expand Down
4 changes: 4 additions & 0 deletions app/policies/media_asset_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 17,10 @@ def can_see_image?
!record.removed? && (record.post.blank? || record.post.visible?(user))
end

def reportable?
record.post.blank?
end

def api_attributes
attributes = super [:variants]
attributes -= [:md5, :file_key, :variants] if !can_see_image?
Expand Down
4 changes: 4 additions & 0 deletions app/policies/moderation_report_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 21,10 @@ def can_see_moderation_reports?
user.is_moderator?
end

def can_view_reported_user?
record.model.class != MediaAsset || can_see_moderation_reports?
end

def permitted_attributes_for_create
[:model_type, :model_id, :reason]
end
Expand Down
12 changes: 12 additions & 0 deletions app/views/media_assets/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 25,24 @@

<%= link_to_media_asset @media_asset, url: @media_asset.original.file_url %>
<% if policy(ModerationReport).can_see_moderation_reports? && @media_asset.pending_moderation_reports.present? %>
<span class="moderation-report-notice font-bold">
Reported (<%= link_to pluralize(@media_asset.pending_moderation_reports.length, "report"), moderation_reports_path(search: { model_type: "MediaAsset", model_id: @media_asset.id, status: "pending" }) %>)
</span>
<% end %>

<span>
<%= render PopupMenuComponent.new(hide_on_click: false) do |menu| %>
<% menu.with_item(hide_on_click: true) do %>
<%= link_to "#{@media_asset.original.file_url}?download=1", download: @media_asset.original.file_name do %>
<%= download_icon %> Download
<% end %>
<% if policy(@media_asset).reportable? %>
<%= link_to new_moderation_report_path(moderation_report: { model_type: "MediaAsset", model_id: @media_asset.id }), remote: true, title: "Report this media asset to the moderators" do %>
<%= flag_icon %> Report
<% end %>
<% end %>
<% end %>
<% menu.with_item do %>
Expand Down
6 changes: 5 additions & 1 deletion app/views/moderation_reports/_new.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 1,9 @@
<div class="report-dialog-body">
<%= embed_wiki("help:report_notice") %>
<% if @moderation_report.model_type == "MediaAsset" %>
<%= embed_wiki("help:media_asset_report_notice") %>
<% else %>
<%= embed_wiki("help:report_notice") %>
<% end %>
<%= edit_form_for(moderation_report, format: :js, remote: true) do |f| %>
<%= f.hidden_field :model_type %>
Expand Down
25 changes: 15 additions & 10 deletions app/views/moderation_reports/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 32,12 @@
<%= link_to report.status.capitalize, moderation_reports_path(search: { status: report.status }) %>
<% end %>
<% t.column "Reported user" do |report| %>
<%= link_to_user report.reported_user %>
<%= link_to "»", moderation_reports_path(search: { recipient_name: report.reported_user.name }) %>
<% t.column "Reported users" do |report| %>
<% if policy(report).can_view_reported_user? %>
<%= report.reported_users.map do |reported_user|
"#{link_to_user(reported_user)} #{link_to('»', moderation_reports_path(search: { recipient_name: reported_user.name }))}"
end.join(', ').html_safe %>
<% end %>
<% end %>
<% t.column "Reporter" do |report| %>
Expand All @@ -43,14 46,16 @@
<div><%= time_ago_in_words_tagged(report.created_at) %></div>
<% end %>
<% t.column column: "control" do |report| %>
<%= render PopupMenuComponent.new do |menu| %>
<% menu.with_item do %>
<%= link_to "Mark as handled", moderation_report_path(report.id), method: :put, remote: true, "data-params": "moderation_report[status]=handled" %>
<% end %>
<% if policy(ModerationReport).update? %>
<% t.column column: "control" do |report| %>
<%= render PopupMenuComponent.new do |menu| %>
<% menu.with_item do %>
<%= link_to "Mark as handled", moderation_report_path(report.id), method: :put, remote: true, "data-params": "moderation_report[status]=handled" %>
<% end %>
<% menu.with_item do %>
<%= link_to "Reject", moderation_report_path(report.id), method: :put, remote: true, "data-params": "moderation_report[status]=rejected" %>
<% menu.with_item do %>
<%= link_to "Reject", moderation_report_path(report.id), method: :put, remote: true, "data-params": "moderation_report[status]=rejected" %>
<% end %>
<% end %>
<% end %>
<% end %>
Expand Down
72 changes: 60 additions & 12 deletions test/functional/moderation_reports_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
require 'test_helper'
require "test_helper"

class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
context "The moderation reports controller" do
Expand All @@ -7,10 7,13 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
@spammer = create(:user, id: 5678, name: "spammer", created_at: 2.weeks.ago)
@mod = create(:moderator_user, created_at: 2.weeks.ago)

@media_asset = create(:media_asset, id: 4567)

as(@spammer) do
@dmail = create(:dmail, from: @spammer, owner: @user, to: @user)
@comment = create(:comment, id: 1234, creator: @spammer)
@forum_post = create(:forum_post, topic: build(:forum_topic), body: "xxx", creator: @spammer)
@upload = create(:upload, uploader: @spammer, upload_media_assets: [build(:upload_media_asset, media_asset: @media_asset)])
end
end

Expand All @@ -37,17 40,19 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
@comment_report = create(:moderation_report, model: @comment, creator: @user)
@forum_report = create(:moderation_report, model: @forum_post, creator: @user)
@dmail_report = create(:moderation_report, reason: "spam", model: @dmail, creator: build(:builder_user, name: "daiyousei", created_at: 2.weeks.ago))
@media_asset_report = create(:moderation_report, model: @media_asset, creator: @user)
end

context "as a user" do
should "show reports submitted by the current user" do
get_auth moderation_reports_path, @user

assert_response :success
assert_select "tbody tr", count: 2
assert_select "tbody tr", count: 3
assert_select "tr#moderation-report-#{@comment_report.id}", count: 1
assert_select "tr#moderation-report-#{@forum_report.id}", count: 1
assert_select "tr#moderation-report-#{@dmail_report.id}", count: 0
assert_select "tr#moderation-report-#{@media_asset_report.id}", count: 1
end

should "not show reports submitted by other users" do
Expand All @@ -68,16 73,18 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end

should respond_to_search({}).with { [@dmail_report, @forum_report, @comment_report] }
should respond_to_search(reason_matches: "spam").with { @dmail_report }
should respond_to_search(recipient_id: 5678).with { [@dmail_report, @forum_report, @comment_report] }
should respond_to_search(recipient_name: "spammer").with { [@dmail_report, @forum_report, @comment_report] }
should(respond_to_search({}).with { [@media_asset_report, @dmail_report, @forum_report, @comment_report] })
should(respond_to_search(reason_matches: "spam").with { @dmail_report })
should(respond_to_search(recipient_id: 5678).with { [@media_asset_report, @dmail_report, @forum_report, @comment_report] })
should(respond_to_search(recipient_name: "spammer").with { [@media_asset_report, @dmail_report, @forum_report, @comment_report]})

context "using includes" do
should respond_to_search(model_id: 1234).with { @comment_report }
should respond_to_search(model_type: "ForumPost").with { @forum_report }
should respond_to_search(ForumPost: {body_matches: "xxx"}).with { @forum_report }
should respond_to_search(creator_name: "daiyousei").with { @dmail_report }
should(respond_to_search(model_id: 1234).with { @comment_report })
should(respond_to_search(model_type: "ForumPost").with { @forum_report })
should(respond_to_search(model_id: 4567).with { @media_asset_report })
should(respond_to_search(model_type: "MediaAsset").with { @media_asset_report })
should(respond_to_search(ForumPost: {body_matches: "xxx"}).with { @forum_report })
should(respond_to_search(creator_name: "daiyousei").with { @dmail_report })
end
end
end
Expand All @@ -88,6 95,40 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
get_auth moderation_report_path(@report), @mod
assert_redirected_to moderation_reports_path(search: { id: @report.id })
end

should "show the reported user for comments" do
@report = create(:moderation_report, model: @comment, creator: @user)
get_auth moderation_reports_path(search: { id: @report.id, model_type: "Comment" }), @user
assert_select ".reported-users-column .user", count: 1
end

should "show the reported user for dmails" do
@report = create(:moderation_report, model: @forum_post, creator: @user)
get_auth moderation_reports_path(search: { id: @report.id, model_type: "ForumPost" }), @user

assert_select ".reported-users-column .user", count: 1
end

should "show the reported user for forum posts" do
@report = create(:moderation_report, model: @dmail, creator: @user)
get_auth moderation_reports_path(search: { id: @report.id, model_type: "Dmail" }), @user

assert_select ".reported-users-column .user", count: 1
end

should "not show the reported user for media assets to normal users" do
@report = create(:moderation_report, model: @media_asset, creator: @user)
get_auth moderation_reports_path(search: { id: @report.id, model_type: "MediaAsset" }), @user

assert_select ".reported-users-column .user", count: 0
end

should "show the reported user for media assets to mods" do
@report = create(:moderation_report, model: @media_asset, creator: @user)
get_auth moderation_reports_path(search: { id: @report.id, model_type: "MediaAsset" }), @mod

assert_select ".reported-users-column .user", count: 1
end
end

context "create action" do
Expand All @@ -111,6 152,13 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
end

should "create a new moderation report on a media asset" do
assert_difference("ModerationReport.count", 1) do
post_auth moderation_reports_path, @user, params: { format: "js", moderation_report: { model_id: @media_asset.id, model_type: "MediaAsset", reason: "xxx" }}
assert_response :success
end
end
end

context "update action" do
Expand All @@ -128,7 176,7 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal("handled", report.reload.status)
assert_equal(true, @user.dmails.received.exists?(from: User.system, title: "Thank you for reporting comment ##{@comment.id}"))
assert_equal(true, ModAction.moderation_report_handled.where(creator: @mod).exists?)
assert_equal(true, ModAction.moderation_report_handled.exists?(creator: @mod))
assert_equal(report, ModAction.last.subject)
assert_equal(@mod, ModAction.last.creator)
end
Expand All @@ -140,7 188,7 @@ class ModerationReportsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal("rejected", report.reload.status)
assert_equal(false, @user.dmails.received.exists?(from: User.system))
assert_equal(true, ModAction.moderation_report_rejected.where(creator: @mod).exists?)
assert_equal(true, ModAction.moderation_report_rejected.exists?(creator: @mod))
assert_equal(report, ModAction.last.subject)
assert_equal(@mod, ModAction.last.creator)
end
Expand Down

0 comments on commit f496011

Please sign in to comment.