comments: allow admins to remove comment votes (fix #4640)

Allow admins to remove comment votes by other users. This is done by
clicking the comment score to get to the comment vote list, then
clicking the Remove button on every vote.
This commit is contained in:
evazion
2021-03-29 23:08:49 -05:00
parent 6b91e55283
commit b3c1c753b3
10 changed files with 78 additions and 15 deletions

View File

@@ -28,12 +28,16 @@ class CommentComponent < ApplicationComponent
def upvoted? def upvoted?
return false if current_user.is_anonymous? return false if current_user.is_anonymous?
comment.votes.active.select(&:is_positive?).map(&:user_id).include?(current_user.id) current_vote&.is_positive?
end end
def downvoted? def downvoted?
return false if current_user.is_anonymous? return false if current_user.is_anonymous?
comment.votes.active.select(&:is_negative?).map(&:user_id).include?(current_user.id) current_vote&.is_negative?
end
def current_vote
@current_vote ||= comment.votes.active.find { |v| v.user_id == current_user.id }
end end
def reported? def reported?

View File

@@ -48,7 +48,7 @@
<% if current_user.is_anonymous? %> <% if current_user.is_anonymous? %>
<%= link_to upvote_icon, login_path(url: request.fullpath), class: "comment-upvote-link inactive-link" %> <%= link_to upvote_icon, login_path(url: request.fullpath), class: "comment-upvote-link inactive-link" %>
<% elsif upvoted? %> <% elsif upvoted? %>
<%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id), class: "comment-upvote-link comment-unvote-link active-link", method: :delete, remote: true %> <%= link_to upvote_icon, comment_vote_path(current_vote), class: "comment-upvote-link comment-unvote-link active-link", method: :delete, remote: true %>
<% else %> <% else %>
<%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "1"), class: "comment-upvote-link inactive-link", method: :post, remote: true %> <%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "1"), class: "comment-upvote-link inactive-link", method: :post, remote: true %>
<% end %> <% end %>
@@ -64,7 +64,7 @@
<% if current_user.is_anonymous? %> <% if current_user.is_anonymous? %>
<%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %> <%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %>
<% elsif downvoted? %> <% elsif downvoted? %>
<%= link_to downvote_icon, comment_comment_votes_path(comment_id: comment.id), class: "comment-downvote-link comment-unvote-link active-link", method: :delete, remote: true %> <%= link_to downvote_icon, comment_vote_path(current_vote), class: "comment-downvote-link comment-unvote-link active-link", method: :delete, remote: true %>
<% else %> <% else %>
<%= link_to downvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "-1"), class: "comment-downvote-link inactive-link", method: :post, remote: true %> <%= link_to downvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "-1"), class: "comment-downvote-link inactive-link", method: :post, remote: true %>
<% end %> <% end %>

View File

@@ -26,8 +26,7 @@ class CommentVotesController < ApplicationController
end end
def destroy def destroy
# XXX should find by comment vote id. @comment_vote = authorize CommentVote.find(params[:id])
@comment_vote = authorize CommentVote.active.find_by!(comment_id: params[:comment_id], user: CurrentUser.user)
@comment_vote.soft_delete(updater: CurrentUser.user) @comment_vote.soft_delete(updater: CurrentUser.user)
respond_with(@comment_vote) respond_with(@comment_vote)

View File

@@ -4,7 +4,7 @@ class CommentVotePolicy < ApplicationPolicy
end end
def destroy? def destroy?
record.user_id == user.id !record.is_deleted? && (record.user_id == user.id || user.is_admin?)
end end
def can_see_votes? def can_see_votes?

View File

@@ -6,6 +6,7 @@
<%= fc.input :post_id, label: "Post", input_html: { value: params.dig(:search, :comment, :post_id) } %> <%= fc.input :post_id, label: "Post", input_html: { value: params.dig(:search, :comment, :post_id) } %>
<% end %> <% end %>
<%= f.input :comment_id, label: "Comment", input_html: { value: params[:search][:comment_id] } %> <%= f.input :comment_id, label: "Comment", input_html: { value: params[:search][:comment_id] } %>
<%= f.input :is_deleted, label: "Deleted?", as: :select, include_blank: true, selected: params[:search][:is_deleted] %>
<%= f.input :score, collection: [["+1", "1"], ["-1", "-1"]], include_blank: true, selected: params[:search][:score] %> <%= f.input :score, collection: [["+1", "1"], ["-1", "-1"]], include_blank: true, selected: params[:search][:score] %>
<%= f.submit "Search" %> <%= f.submit "Search" %>
<% end %> <% end %>

View File

@@ -0,0 +1,2 @@
Danbooru.Utility.notice("Vote removed");
location.reload();

View File

@@ -14,9 +14,23 @@
<%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %> <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %>
<% end %> <% end %>
<% t.column "Status" do |vote| %>
<%= "Deleted" if vote.is_deleted? %>
<% end %>
<% t.column "Created" do |vote| %> <% t.column "Created" do |vote| %>
<%= time_ago_in_words_tagged(vote.created_at) %> <%= time_ago_in_words_tagged(vote.created_at) %>
<% end %> <% end %>
<% t.column column: "control" do |vote| %>
<% if policy(vote).destroy? %>
<%= render PopupMenuComponent.new do |menu| %>
<%= menu.item do %>
<%= link_to "Remove", comment_vote_path(vote, variant: "listing"), remote: true, method: :delete %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %> <% end %>
<%= numbered_paginator(@comment_votes) %> <%= numbered_paginator(@comment_votes) %>

View File

@@ -8,27 +8,40 @@
<% t.column "Post" do |vote| %> <% t.column "Post" do |vote| %>
<%= post_preview(vote.comment.post, show_deleted: true) %> <%= post_preview(vote.comment.post, show_deleted: true) %>
<% end %> <% end %>
<% t.column "Comment", td: {class: "col-expand"} do |vote| %> <% t.column "Comment", td: {class: "col-expand"} do |vote| %>
<div class="prose"> <div class="prose">
<%= format_text(vote.comment.body) %> <%= format_text(vote.comment.body) %>
</div> </div>
<% end %> <% end %>
<% t.column "Status" do |vote| %>
<%= "Deleted" if vote.is_deleted? %>
<% end %>
<% t.column "Score" do |vote| %> <% t.column "Score" do |vote| %>
<%= link_to sprintf("%+d", vote.score), comment_votes_path(search: { score: vote.score }) %> <%= link_to sprintf("%+d", vote.score), comment_votes_path(search: { score: vote.score }) %>
<% end %> <% end %>
<% t.column "Commenter" do |vote| %> <% t.column "Commenter" do |vote| %>
<%= link_to_user vote.comment.creator %> <%= link_to_user vote.comment.creator %>
<%= link_to "»", comment_votes_path(search: { comment: { creator_name: vote.comment.creator.name }}) %> <%= link_to "»", comment_votes_path(search: { comment: { creator_name: vote.comment.creator.name }}) %>
<div><%= time_ago_in_words_tagged(vote.comment.created_at) %></div> <div><%= time_ago_in_words_tagged(vote.comment.created_at) %></div>
<% end %> <% end %>
<% t.column "Voter" do |vote| %> <% t.column "Voter" do |vote| %>
<%= link_to_user vote.user %> <%= link_to_user vote.user %>
<%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %> <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %>
<div><%= time_ago_in_words_tagged(vote.created_at) %></div> <div><%= time_ago_in_words_tagged(vote.created_at) %></div>
<% end %> <% end %>
<% t.column column: "control" do |vote| %> <% t.column column: "control" do |vote| %>
<% if vote.user == CurrentUser.user %> <% if policy(vote).destroy? %>
<%= link_to "unvote", comment_comment_votes_path(vote.comment), remote: true, method: :delete %> <%= render PopupMenuComponent.new do |menu| %>
<%= menu.item do %>
<%= link_to "Remove", comment_vote_path(vote, variant: "listing"), remote: true, method: :delete %>
<% end %>
<% end %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -81,7 +81,7 @@ Rails.application.routes.draw do
post :approve post :approve
end end
end end
resources :comment_votes, only: [:index] resources :comment_votes, only: [:index, :destroy]
resources :comments do resources :comments do
resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes" resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes"
collection do collection do

View File

@@ -141,21 +141,51 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
context "#destroy" do context "#destroy" do
should "allow users to remove their own comment votes" do should "allow users to remove their own comment votes" do
@vote = create(:comment_vote, user: @user) @vote = create(:comment_vote, user: @user)
assert_equal(1, @vote.comment.score)
assert_difference("CommentVote.count", 0) do assert_difference("CommentVote.count", 0) do
delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true delete_auth comment_vote_path(@vote), @user, xhr: true
assert_response :success assert_response :success
assert_equal(true, @vote.reload.is_deleted?) assert_equal(true, @vote.reload.is_deleted?)
assert_equal(0, @vote.comment.score)
assert_equal(false, ModAction.comment_vote_delete.exists?)
end end
end end
should "not allow users to remove comment votes by other users" do should "not allow normal users to remove comment votes by other users" do
@vote = create(:comment_vote) @vote = create(:comment_vote)
assert_equal(1, @vote.comment.score)
assert_difference("CommentVote.count", 0) do assert_difference("CommentVote.count", 0) do
delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true delete_auth comment_vote_path(@vote), @user, xhr: true, params: { variant: "listing" }
assert_response 404
assert_response 403
assert_equal(false, @vote.reload.is_deleted?) assert_equal(false, @vote.reload.is_deleted?)
assert_equal(1, @vote.comment.score)
end
end
should "not allow deleting already deleted votes" do
@vote = create(:comment_vote, is_deleted: true)
delete_auth comment_vote_path(@vote), @user, xhr: true
assert_response 403
end
should "allow admins to remove comment votes by other users" do
@vote = create(:comment_vote)
assert_equal(1, @vote.comment.score)
assert_difference("CommentVote.count", 0) do
delete_auth comment_vote_path(@vote), create(:admin_user), xhr: true, params: { variant: "listing" }
assert_response :success
assert_equal(true, @vote.reload.is_deleted?)
assert_equal(0, @vote.comment.score)
assert_equal("comment_vote_delete", ModAction.last.category)
assert_match(/deleted comment vote/, ModAction.last.description)
end end
end end
@@ -167,7 +197,7 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
should "delete the current active vote" do should "delete the current active vote" do
@vote = create(:comment_vote, comment: @comment, user: @user) @vote = create(:comment_vote, comment: @comment, user: @user)
delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true delete_auth comment_vote_path(@vote), @user, xhr: true
assert_response :success assert_response :success
assert_equal(true, @vote.reload.is_deleted?) assert_equal(true, @vote.reload.is_deleted?)