From b3c1c753b333130dafbe97bc2aba0cd7981d1f5e Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 29 Mar 2021 23:08:49 -0500 Subject: [PATCH] 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. --- app/components/comment_component.rb | 8 +++- .../comment_component.html.erb | 4 +- app/controllers/comment_votes_controller.rb | 3 +- app/policies/comment_vote_policy.rb | 2 +- app/views/comment_votes/_search.html.erb | 1 + .../comment_votes/destroy.js+listing.erb | 2 + .../comment_votes/index.html+compact.erb | 14 +++++++ app/views/comment_votes/index.html.erb | 17 +++++++- config/routes.rb | 2 +- .../comment_votes_controller_test.rb | 40 ++++++++++++++++--- 10 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 app/views/comment_votes/destroy.js+listing.erb diff --git a/app/components/comment_component.rb b/app/components/comment_component.rb index 77fbdafdc..39793e0c3 100644 --- a/app/components/comment_component.rb +++ b/app/components/comment_component.rb @@ -28,12 +28,16 @@ class CommentComponent < ApplicationComponent def upvoted? 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 def downvoted? 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 def reported? diff --git a/app/components/comment_component/comment_component.html.erb b/app/components/comment_component/comment_component.html.erb index 987e80783..bf29ec764 100644 --- a/app/components/comment_component/comment_component.html.erb +++ b/app/components/comment_component/comment_component.html.erb @@ -48,7 +48,7 @@ <% if current_user.is_anonymous? %> <%= link_to upvote_icon, login_path(url: request.fullpath), class: "comment-upvote-link inactive-link" %> <% 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 %> <%= 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 %> @@ -64,7 +64,7 @@ <% if current_user.is_anonymous? %> <%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %> <% 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 %> <%= 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 %> diff --git a/app/controllers/comment_votes_controller.rb b/app/controllers/comment_votes_controller.rb index 4235e8492..5b53dab0d 100644 --- a/app/controllers/comment_votes_controller.rb +++ b/app/controllers/comment_votes_controller.rb @@ -26,8 +26,7 @@ class CommentVotesController < ApplicationController end def destroy - # XXX should find by comment vote id. - @comment_vote = authorize CommentVote.active.find_by!(comment_id: params[:comment_id], user: CurrentUser.user) + @comment_vote = authorize CommentVote.find(params[:id]) @comment_vote.soft_delete(updater: CurrentUser.user) respond_with(@comment_vote) diff --git a/app/policies/comment_vote_policy.rb b/app/policies/comment_vote_policy.rb index 25706185c..3ca44bb6b 100644 --- a/app/policies/comment_vote_policy.rb +++ b/app/policies/comment_vote_policy.rb @@ -4,7 +4,7 @@ class CommentVotePolicy < ApplicationPolicy end def destroy? - record.user_id == user.id + !record.is_deleted? && (record.user_id == user.id || user.is_admin?) end def can_see_votes? diff --git a/app/views/comment_votes/_search.html.erb b/app/views/comment_votes/_search.html.erb index 2fcdde6a0..b33796678 100644 --- a/app/views/comment_votes/_search.html.erb +++ b/app/views/comment_votes/_search.html.erb @@ -6,6 +6,7 @@ <%= fc.input :post_id, label: "Post", input_html: { value: params.dig(:search, :comment, :post_id) } %> <% end %> <%= 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.submit "Search" %> <% end %> diff --git a/app/views/comment_votes/destroy.js+listing.erb b/app/views/comment_votes/destroy.js+listing.erb new file mode 100644 index 000000000..2edde040f --- /dev/null +++ b/app/views/comment_votes/destroy.js+listing.erb @@ -0,0 +1,2 @@ +Danbooru.Utility.notice("Vote removed"); +location.reload(); diff --git a/app/views/comment_votes/index.html+compact.erb b/app/views/comment_votes/index.html+compact.erb index 053c3b37e..f3cf672f4 100644 --- a/app/views/comment_votes/index.html+compact.erb +++ b/app/views/comment_votes/index.html+compact.erb @@ -14,9 +14,23 @@ <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %> <% end %> + <% t.column "Status" do |vote| %> + <%= "Deleted" if vote.is_deleted? %> + <% end %> + <% t.column "Created" do |vote| %> <%= time_ago_in_words_tagged(vote.created_at) %> <% 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 %> <%= numbered_paginator(@comment_votes) %> diff --git a/app/views/comment_votes/index.html.erb b/app/views/comment_votes/index.html.erb index 8c17a05d7..ce6b3e902 100644 --- a/app/views/comment_votes/index.html.erb +++ b/app/views/comment_votes/index.html.erb @@ -8,27 +8,40 @@ <% t.column "Post" do |vote| %> <%= post_preview(vote.comment.post, show_deleted: true) %> <% end %> + <% t.column "Comment", td: {class: "col-expand"} do |vote| %>
<%= format_text(vote.comment.body) %>
<% end %> + + <% t.column "Status" do |vote| %> + <%= "Deleted" if vote.is_deleted? %> + <% end %> + <% t.column "Score" do |vote| %> <%= link_to sprintf("%+d", vote.score), comment_votes_path(search: { score: vote.score }) %> <% end %> + <% t.column "Commenter" do |vote| %> <%= link_to_user vote.comment.creator %> <%= link_to "»", comment_votes_path(search: { comment: { creator_name: vote.comment.creator.name }}) %>
<%= time_ago_in_words_tagged(vote.comment.created_at) %>
<% end %> + <% t.column "Voter" do |vote| %> <%= link_to_user vote.user %> <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %>
<%= time_ago_in_words_tagged(vote.created_at) %>
<% end %> + <% t.column column: "control" do |vote| %> - <% if vote.user == CurrentUser.user %> - <%= link_to "unvote", comment_comment_votes_path(vote.comment), remote: true, method: :delete %> + <% 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 %> diff --git a/config/routes.rb b/config/routes.rb index 82df33b1c..753d3d991 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,7 +81,7 @@ Rails.application.routes.draw do post :approve end end - resources :comment_votes, only: [:index] + resources :comment_votes, only: [:index, :destroy] resources :comments do resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes" collection do diff --git a/test/functional/comment_votes_controller_test.rb b/test/functional/comment_votes_controller_test.rb index 2b6e0227c..f46c4bbf5 100644 --- a/test/functional/comment_votes_controller_test.rb +++ b/test/functional/comment_votes_controller_test.rb @@ -141,21 +141,51 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest context "#destroy" do should "allow users to remove their own comment votes" do @vote = create(:comment_vote, user: @user) + assert_equal(1, @vote.comment.score) 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_equal(true, @vote.reload.is_deleted?) + assert_equal(0, @vote.comment.score) + + assert_equal(false, ModAction.comment_vote_delete.exists?) 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) + assert_equal(1, @vote.comment.score) assert_difference("CommentVote.count", 0) do - delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true - assert_response 404 + delete_auth comment_vote_path(@vote), @user, xhr: true, params: { variant: "listing" } + + assert_response 403 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 @@ -167,7 +197,7 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest should "delete the current active vote" do @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_equal(true, @vote.reload.is_deleted?)