[Posts] Add back comment disabling (#749)

This commit is contained in:
Donovan Daniels 2024-10-28 15:56:22 -05:00 committed by GitHub
parent 567ec94e5a
commit 6f296e8e01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 229 additions and 57 deletions

View File

@ -174,7 +174,8 @@ class PostsController < ApplicationController
] ]
permitted_params += %i[is_rating_locked] if CurrentUser.is_privileged? permitted_params += %i[is_rating_locked] if CurrentUser.is_privileged?
permitted_params += %i[is_note_locked bg_color] if CurrentUser.is_janitor? permitted_params += %i[is_note_locked bg_color] if CurrentUser.is_janitor?
permitted_params += %i[is_status_locked is_comment_locked locked_tags hide_from_anonymous hide_from_search_engines] if CurrentUser.is_admin? permitted_params += %i[is_comment_locked] if CurrentUser.is_moderator?
permitted_params += %i[is_status_locked is_comment_disabled locked_tags hide_from_anonymous hide_from_search_engines] if CurrentUser.is_admin?
params.require(:post).permit(permitted_params) params.require(:post).permit(permitted_params)
end end

View File

@ -93,6 +93,7 @@ class VoteManager
raise UserVote::Error, "Invalid vote" unless [1, -1].include?(score) raise UserVote::Error, "Invalid vote" unless [1, -1].include?(score)
raise UserVote::Error, "You do not have permission to vote" unless user.is_member? raise UserVote::Error, "You do not have permission to vote" unless user.is_member?
raise UserVote::Error, "Comment section is locked" if comment.post.is_comment_locked? raise UserVote::Error, "Comment section is locked" if comment.post.is_comment_locked?
raise UserVote::Error, "Comment section is disabled" if comment.post.is_comment_disabled?
CommentVote.transaction(**ISOLATION) do CommentVote.transaction(**ISOLATION) do
CommentVote.uncached do CommentVote.uncached do
score_modifier = score score_modifier = score

View File

@ -31,6 +31,10 @@ class Comment < ApplicationRecord
belongs_to :warning_user, class_name: "User", optional: true belongs_to :warning_user, class_name: "User", optional: true
has_many :votes, :class_name => "CommentVote", :dependent => :destroy has_many :votes, :class_name => "CommentVote", :dependent => :destroy
scope :deleted, -> { where(is_hidden: true) }
scope :undeleted, -> { where(is_hidden: false) }
scope :stickied, -> { where(is_sticky: true) }
module SearchMethods module SearchMethods
def recent def recent
reorder("comments.id desc").limit(RECENT_COUNT) reorder("comments.id desc").limit(RECENT_COUNT)
@ -47,21 +51,16 @@ class Comment < ApplicationRecord
end end
def visible(user) def visible(user)
if user.is_moderator? q = where("comments.score >= ? or comments.is_sticky = true", user.comment_threshold)
where("comments.score >= ? or comments.is_sticky = true", user.comment_threshold) unless user.is_moderator?
elsif user.is_janitor? q = q.joins(:post).where("comments.is_sticky = true or posts.is_comment_disabled = false or comments.creator_id = ?", user.id)
where("(comments.score >= ? or comments.is_sticky = true) and (comments.is_sticky = true or comments.is_hidden = false or comments.creator_id = ?)", user.comment_threshold, user.id) if user.is_janitor?
else q = q.where("comments.is_sticky = true or comments.is_hidden = false or comments.creator_id = ?", user.id)
where("(comments.score >= ? or comments.is_sticky = true) and (comments.is_hidden = false or comments.creator_id = ?)", user.comment_threshold, user.id) else
q = q.where("comments.is_hidden = false or comments.creator_id = ?", user.id)
end
end end
end q
def deleted
where("comments.is_hidden = true")
end
def undeleted
where("comments.is_hidden = false")
end end
def post_tags_match(query) def post_tags_match(query)
@ -139,7 +138,11 @@ class Comment < ApplicationRecord
end end
def post_not_comment_locked def post_not_comment_locked
errors.add(:base, "Post has comments locked") if !CurrentUser.is_moderator? && Post.find_by(id: post_id)&.is_comment_locked? return if CurrentUser.is_moderator?
post = Post.find_by(id: post_id)
return if post.blank?
errors.add(:base, "Post has comments locked") if post.is_comment_locked?
errors.add(:base, "Post has comments disabled") if post.is_comment_disabled?
end end
def update_last_commented_at_on_create def update_last_commented_at_on_create
@ -179,25 +182,26 @@ class Comment < ApplicationRecord
def can_reply?(user) def can_reply?(user)
return false if is_sticky? return false if is_sticky?
return false if post.is_comment_locked? && !user.is_moderator? return false if (post.is_comment_locked? || post.is_comment_disabled?) && !user.is_moderator?
true true
end end
def editable_by?(user) def editable_by?(user)
return true if user.is_admin? return true if user.is_admin?
return false if post.is_comment_locked? && !user.is_moderator? return false if (post.is_comment_locked? || post.is_comment_disabled?) && !user.is_moderator?
return false if was_warned? return false if was_warned?
creator_id == user.id creator_id == user.id
end end
def can_hide?(user) def can_hide?(user)
return true if user.is_moderator? return true if user.is_moderator?
return false if was_warned? return false if !visible_to?(user) || was_warned? || post&.is_comment_disabled?
user.id == creator_id user.id == creator_id
end end
def visible_to?(user) def visible_to?(user)
return true if user.is_moderator? return true if user.is_moderator?
return false if !is_sticky? && (post&.is_comment_disabled? && creator_id != user.id)
return true if user.is_janitor? && is_sticky? return true if user.is_janitor? && is_sticky?
return true if is_hidden? == false return true if is_hidden? == false
creator_id == user.id # Can always see your own comments, even if hidden. creator_id == user.id # Can always see your own comments, even if hidden.

View File

@ -58,9 +58,6 @@ class Post < ApplicationRecord
attr_accessor :old_tag_string, :old_parent_id, :old_source, :old_rating, attr_accessor :old_tag_string, :old_parent_id, :old_source, :old_rating,
:do_not_version_changes, :tag_string_diff, :source_diff, :edit_reason :do_not_version_changes, :tag_string_diff, :source_diff, :edit_reason
# FIXME: Remove this
alias_attribute :is_comment_locked, :is_comment_disabled
has_many :versions, -> {order("post_versions.id ASC")}, :class_name => "PostVersion", :dependent => :destroy has_many :versions, -> {order("post_versions.id ASC")}, :class_name => "PostVersion", :dependent => :destroy
IMAGE_TYPES = %i[original large preview crop] IMAGE_TYPES = %i[original large preview crop]
@ -1573,6 +1570,10 @@ class Post < ApplicationRecord
action = is_comment_locked? ? :comment_locked : :comment_unlocked action = is_comment_locked? ? :comment_locked : :comment_unlocked
PostEvent.add(id, CurrentUser.user, action) PostEvent.add(id, CurrentUser.user, action)
end end
if saved_change_to_is_comment_disabled?
action = is_comment_disabled? ? :comment_disabled : :comment_enabled
PostEvent.add(id, CurrentUser.user, action)
end
if saved_change_to_bg_color? if saved_change_to_bg_color?
PostEvent.add(id, CurrentUser.user, :changed_bg_color, { bg_color: bg_color }) PostEvent.add(id, CurrentUser.user, :changed_bg_color, { bg_color: bg_color })
end end
@ -1770,8 +1771,12 @@ class Post < ApplicationRecord
!has_tag?("grandfathered_content") && created_at.after?("2015-01-01") !has_tag?("grandfathered_content") && created_at.after?("2015-01-01")
end end
def visible_comment_count(_user) def visible_comment_count(user)
comment_count if user.is_moderator? || !is_comment_disabled?
comment_count
else
comments.visible(user).count
end
end end
def avoid_posting_artists def avoid_posting_artists

View File

@ -19,6 +19,8 @@ class PostEvent < ApplicationRecord
note_unlocked: 13, note_unlocked: 13,
comment_locked: 18, comment_locked: 18,
comment_unlocked: 19, comment_unlocked: 19,
comment_disabled: 22,
comment_enabled: 23,
replacement_accepted: 14, replacement_accepted: 14,
replacement_rejected: 15, replacement_rejected: 15,
replacement_promoted: 20, replacement_promoted: 20,
@ -26,9 +28,11 @@ class PostEvent < ApplicationRecord
expunged: 17, expunged: 17,
changed_bg_color: 21, changed_bg_color: 21,
} }
MOD_ONLY_ACTIONS = [ MOD_ONLY_SEARCH_ACTIONS = [
actions[:comment_locked], actions[:comment_locked],
actions[:comment_unlocked], actions[:comment_unlocked],
actions[:comment_disabled],
actions[:comment_enabled],
].freeze ].freeze
def self.add(post_id, creator, action, data = {}) def self.add(post_id, creator, action, data = {})
@ -47,11 +51,8 @@ class PostEvent < ApplicationRecord
def self.search(params) def self.search(params)
q = super q = super
unless CurrentUser.is_moderator?
q = q.where.not(action: MOD_ONLY_ACTIONS)
end
if params[:post_id].present? if params[:post_id].present?
q = q.where("post_id = ?", params[:post_id].to_i) q = q.where(post_id: params[:post_id])
end end
q = q.where_user(:creator_id, :creator, params) do |condition, user_ids| q = q.where_user(:creator_id, :creator, params) do |condition, user_ids|
@ -62,9 +63,18 @@ class PostEvent < ApplicationRecord
end end
if params[:action].present? if params[:action].present?
q = q.where('action = ?', actions[params[:action]]) if !CurrentUser.user.is_moderator? && MOD_ONLY_SEARCH_ACTIONS.include?(actions[params[:action]])
raise(User::PrivilegeError)
end
q = q.where(action: actions[params[:action]])
end end
q.apply_basic_order(params) q.apply_basic_order(params)
end end
def self.search_options_for(user)
options = actions.keys
return options if user.is_moderator?
options.reject { |action| MOD_ONLY_SEARCH_ACTIONS.any?(actions[action]) }
end
end end

View File

@ -19,7 +19,7 @@
</div> </div>
</div> </div>
<div class="comments"> <div class="comments">
<%= render "comments/partials/index/list", :post => post, :comments => post.comments.visible(CurrentUser.user).recent.reverse %> <%= render "comments/partials/index/list", post: post, comments: post.comments.visible(CurrentUser.user).recent.reverse %>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@ -1,28 +1,40 @@
<div class="comments-for-post" data-post-id="<%= post.id %>"> <div class="comments-for-post" data-post-id="<%= post.id %>">
<% if post.is_comment_disabled? %>
Comment section has been disabled.
<% end %>
<% if post.is_comment_locked? %> <% if post.is_comment_locked? %>
Comment section has been locked. Comment section has been locked.
<% end %> <% end %>
<div class="row notices"> <% if !CurrentUser.user.is_moderator? && post.is_comment_disabled? %>
<% if post.comments.hidden(CurrentUser.user).count > 0 || (params[:controller] == "comments" && post.comments.count > Comment::RECENT_COUNT) %> <% comments = comments.stickied %>
<div class="list-of-comments">
<% if comments.any? %>
<%= render partial: "comments/partials/show/comment", collection: comments, locals: { post: post } %>
<% end %>
</div>
<% else %>
<div class="row notices">
<% if post.comments.hidden(CurrentUser.user).count > 0 || (params[:controller] == "comments" && post.comments.count > Comment::RECENT_COUNT) %>
<span id="threshold-comments-notice-for-<%= post.id %>"> <span id="threshold-comments-notice-for-<%= post.id %>">
<%= link_to "Show all comments", comments_path(:post_id => post.id), 'data-pid': post.id, class: 'show-all-comments-for-post-link' %> <%= link_to "Show all comments", comments_path(:post_id => post.id), 'data-pid': post.id, class: 'show-all-comments-for-post-link' %>
</span> </span>
<% end %>
</div>
<div class="list-of-comments">
<% if comments.empty? %>
<% if post.last_commented_at.present? %>
<p>There are no visible comments.</p>
<% else %>
<p>There are no comments.</p>
<% end %> <% end %>
<% else %> </div>
<%= render partial: "comments/partials/show/comment", collection: comments, locals: { post: post } %>
<% end %>
</div>
<% if post.is_comment_locked? && !CurrentUser.is_moderator? %> <div class="list-of-comments">
<% if comments.empty? %>
<% if post.last_commented_at.present? %>
<p>There are no visible comments.</p>
<% else %>
<p>There are no comments.</p>
<% end %>
<% else %>
<%= render partial: "comments/partials/show/comment", collection: comments, locals: { post: post } %>
<% end %>
</div>
<% end %>
<% if (post.is_comment_locked? || post.is_comment_disabled?) && !CurrentUser.is_moderator? %>
<% elsif CurrentUser.is_member? %> <% elsif CurrentUser.is_member? %>
<div class="new-comment"> <div class="new-comment">
<% if !CurrentUser.is_anonymous? && !CurrentUser.user.is_janitor? %> <% if !CurrentUser.is_anonymous? && !CurrentUser.user.is_janitor? %>

View File

@ -1,5 +1,5 @@
<%= form_search path: post_events_path do |f| %> <%= form_search path: post_events_path do |f| %>
<%= f.input :post_id, label: "Post #" %> <%= f.input :post_id, label: "Post #" %>
<%= f.user :creator %> <%= f.user :creator %>
<%= f.input :action, label: "Action", collection: PostEvent.actions.keys.map {|k| [k.to_s.capitalize.tr("_"," "), k.to_s]}, include_blank: true %> <%= f.input :action, label: "Action", collection: PostEvent.search_options_for(CurrentUser.user).map { |k| [k.to_s.capitalize.tr("_"," "), k.to_s] }, include_blank: true %>
<% end %> <% end %>

View File

@ -55,7 +55,9 @@
<% end %> <% end %>
<%= f.input :is_rating_locked, label: "Rating" %> <%= f.input :is_rating_locked, label: "Rating" %>
<% if CurrentUser.is_admin? %> <% if CurrentUser.is_admin? %>
<%= f.input :is_status_locked, label: "Status" %> <%= f.input :is_status_locked, label: "Status" %>
<% end %>
<% if CurrentUser.is_moderator? %>
<%= f.input :is_comment_locked, label: "Comments" %> <%= f.input :is_comment_locked, label: "Comments" %>
<% end %> <% end %>
</fieldset> </fieldset>
@ -72,6 +74,7 @@
<fieldset class="limits"> <fieldset class="limits">
<%= f.input :hide_from_anonymous, as: :boolean, label: "Hide from Anon" %> <%= f.input :hide_from_anonymous, as: :boolean, label: "Hide from Anon" %>
<%= f.input :hide_from_search_engines, as: :boolean, label: "Hide from search engines" %> <%= f.input :hide_from_search_engines, as: :boolean, label: "Hide from search engines" %>
<%= f.input :is_comment_disabled, label: "Hide comments" %>
</fieldset> </fieldset>
</div> </div>
<% end %> <% end %>

View File

@ -0,0 +1,8 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "config", "environment"))
Post.without_timeout do
Post.where(is_comment_disabled: true).update_all("is_comment_locked = is_comment_disabled, is_comment_disabled = false")
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddIsCommentLockedToPosts < ActiveRecord::Migration[7.1]
def change
add_column(:posts, :is_comment_locked, :boolean, null: false, default: false)
end
end

View File

@ -1685,7 +1685,8 @@ CREATE TABLE public.posts (
bg_color character varying, bg_color character varying,
generated_samples character varying[], generated_samples character varying[],
duration numeric, duration numeric,
is_comment_disabled boolean DEFAULT false NOT NULL is_comment_disabled boolean DEFAULT false NOT NULL,
is_comment_locked boolean DEFAULT false NOT NULL
); );
@ -4676,6 +4677,7 @@ ALTER TABLE ONLY public.avoid_postings
SET search_path TO "$user", public; SET search_path TO "$user", public;
INSERT INTO "schema_migrations" (version) VALUES INSERT INTO "schema_migrations" (version) VALUES
('20240905160626'),
('20240726170041'), ('20240726170041'),
('20240709134926'), ('20240709134926'),
('20240706061122'), ('20240706061122'),

View File

@ -14,10 +14,10 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
CurrentUser.user = @user CurrentUser.user = @user
end end
context "#create.json" do context "create action" do
should "create a vote" do should "create a vote" do
assert_difference(-> { CommentVote.count }, 1) do assert_difference("CommentVote.count", 1) do
post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json} post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json }
assert_response :success assert_response :success
end end
end end
@ -29,6 +29,40 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
assert_response :success assert_response :success
end end
end end
should "prevent voting on comment locked posts" do
@post.update(is_comment_locked: true)
assert_no_difference("CommentVote.count") do
post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json }
assert_response 422
end
end
should "prevent unvoting on comment locked posts" do
@post.update(is_comment_locked: true)
create(:comment_vote, comment: @comment, user: @user, score: -1)
assert_no_difference("CommentVote.count") do
post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json }
assert_response 422
end
end
should "prevent voting on comment disabled posts" do
@post.update(is_comment_disabled: true)
assert_no_difference("CommentVote.count") do
post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json }
assert_response 422
end
end
should "prevent unvoting on comment disabled posts" do
@post.update(is_comment_disabled: true)
create(:comment_vote, comment: @comment, user: @user, score: -1)
assert_no_difference("CommentVote.count") do
post_auth comment_votes_path(@comment), @user, params: { score: -1, format: :json }
assert_response 422
end
end
end end
end end
end end

View File

@ -108,6 +108,22 @@ class CommentsControllerTest < ActionDispatch::IntegrationTest
assert_equal(false, @comment.reload.do_not_bump_post) assert_equal(false, @comment.reload.do_not_bump_post)
assert_equal(@post.id, @comment.post_id) assert_equal(@post.id, @comment.post_id)
end end
should "not allow changing comments on comment locked posts" do
@post.update(is_comment_locked: true)
body = @comment.body
put_auth comment_path(@comment.id), @user, params: { comment: { body: "abc" } }
assert_response(:forbidden)
assert_equal(body, @comment.reload.body)
end
should "not allow changing comments on comment disabled posts" do
@post.update(is_comment_disabled: true)
body = @comment.body
put_auth comment_path(@comment.id), @user, params: { comment: { body: "abc" } }
assert_response(:forbidden)
assert_equal(body, @comment.reload.body)
end
end end
context "new action" do context "new action" do
@ -132,14 +148,39 @@ class CommentsControllerTest < ActionDispatch::IntegrationTest
end end
assert_redirected_to comments_path assert_redirected_to comments_path
end end
should "not allow commenting on comment locked posts" do
@post.update(is_comment_locked: true)
assert_difference("Comment.count", 0) do
post_auth comments_path, @user, params: { comment: { body: "abc", post_id: @post.id } }
assert_redirected_to(post_path(@post))
assert_equal("Post has comments locked", flash[:notice])
end
end
should "not allow commenting on comment disabled posts" do
@post.update(is_comment_disabled: true)
assert_difference("Comment.count", 0) do
post_auth comments_path, @user, params: { comment: { body: "abc", post_id: @post.id } }
assert_redirected_to(post_path(@post))
assert_equal("Post has comments disabled", flash[:notice])
end
end
end end
context "hide action" do context "hide action" do
should "mark comment as hidden" do should "mark comment as hidden" do
post_auth hide_comment_path(@comment.id), @user post_auth hide_comment_path(@comment), @user
assert_equal(true, @comment.reload.is_hidden) assert_equal(true, @comment.reload.is_hidden)
assert_redirected_to @comment assert_redirected_to @comment
end end
should "not allow hiding comments on comment disabled posts" do
@post.update(is_comment_disabled: true)
post_auth hide_comment_path(@comment), @user
assert_equal(false, @comment.reload.is_hidden)
assert_response(403)
end
end end
context "unhide action" do context "unhide action" do
@ -148,13 +189,13 @@ class CommentsControllerTest < ActionDispatch::IntegrationTest
end end
should "mark comment as unhidden if mod" do should "mark comment as unhidden if mod" do
post_auth unhide_comment_path(@comment.id), @mod post_auth unhide_comment_path(@comment), @mod
assert_equal(false, @comment.reload.is_hidden) assert_equal(false, @comment.reload.is_hidden)
assert_redirected_to(@comment) assert_redirected_to(@comment)
end end
should "not mark comment as unhidden if not mod" do should "not mark comment as unhidden if not mod" do
post_auth unhide_comment_path(@comment.id), @user post_auth unhide_comment_path(@comment), @user
assert_equal(true, @comment.reload.is_hidden) assert_equal(true, @comment.reload.is_hidden)
assert_response :forbidden assert_response :forbidden
end end
@ -162,7 +203,7 @@ class CommentsControllerTest < ActionDispatch::IntegrationTest
context "destroy action" do context "destroy action" do
should "destroy the comment" do should "destroy the comment" do
delete_auth comment_path(@comment.id), create(:admin_user) delete_auth comment_path(@comment), create(:admin_user)
assert_equal(0, Comment.where(id: @comment.id).count) assert_equal(0, Comment.where(id: @comment.id).count)
end end
end end

View File

@ -79,6 +79,38 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
put_auth post_path(@post), @user, params: {:post => {:last_noted_at => 1.minute.ago}} put_auth post_path(@post), @user, params: {:post => {:last_noted_at => 1.minute.ago}}
assert_nil(@post.reload.last_noted_at) assert_nil(@post.reload.last_noted_at)
end end
should "allow moderators to lock comments" do
assert_difference("PostEvent.count", 1) do
put_auth post_path(@post), create(:moderator_user), params: { post: { is_comment_locked: true } }
end
assert_equal(true, @post.reload.is_comment_locked?)
assert_equal("comment_locked", PostEvent.last.action)
end
should "allow moderators to unlock comments" do
@post.update_columns(is_comment_locked: true)
assert_difference("PostEvent.count", 1) do
put_auth post_path(@post), create(:moderator_user), params: { post: { is_comment_locked: false } }
end
assert_equal(false, @post.reload.is_comment_locked?)
assert_equal("comment_unlocked", PostEvent.last.action)
end
should "not allow moderators to disable comments" do
assert_no_difference("PostEvent.count") do
put_auth post_path(@post), create(:moderator_user), params: { post: { is_comment_disabled: true } }
end
assert_equal(false, @post.reload.is_comment_disabled?)
end
should "not allow moderators to enable comments" do
@post.update_columns(is_comment_disabled: true)
assert_no_difference("PostEvent.count") do
put_auth post_path(@post), create(:moderator_user), params: { post: { is_comment_disabled: false } }
end
assert_equal(true, @post.reload.is_comment_disabled?)
end
end end
context "revert action" do context "revert action" do

View File

@ -281,6 +281,18 @@ class CommentTest < ActiveSupport::TestCase
assert_equal(["Post has comments locked"], comment.errors.full_messages) assert_equal(["Post has comments locked"], comment.errors.full_messages)
end end
end end
context "on a comment disabled post" do
setup do
@post = create(:post, is_comment_disabled: true)
end
should "prevent new comments" do
comment = build(:comment, post: @post)
comment.save
assert_equal(["Post has comments disabled"], comment.errors.full_messages)
end
end
end end
context "during validation" do context "during validation" do