votes: allow admins to remove post votes.
Allow admins to remove votes on posts. This is for fixing vote abuse. Votes can be removed by going to the vote list on the /post_votes page, or by clicking on a post's score, then using the "Remove" option in the "..." dropdown menu next to the vote. Votes are soft-deleted - they're marked as deleted in the database, but not fully deleted. Removed votes are only visible to admins, not to regular users. When a vote is removed by an admin, it leaves a mod action. Technically it's possible to undelete votes, but there's no UI for it.
This commit is contained in:
@@ -28,15 +28,13 @@ class Favorite < ApplicationRecord
|
||||
end
|
||||
|
||||
def upvote_post_on_create
|
||||
if Pundit.policy!(user, PostVote).create?
|
||||
PostVote.negative.destroy_by(post: post, user: user)
|
||||
|
||||
# Silently ignore the error if the user has already upvoted the post.
|
||||
PostVote.create(post: post, user: user, score: 1)
|
||||
if Pundit.policy!(user, PostVote).create? && !PostVote.active.exists?(post: post, user: user, score: 1)
|
||||
PostVote.create!(post: post, user: user, score: 1)
|
||||
end
|
||||
end
|
||||
|
||||
def unvote_post_on_destroy
|
||||
PostVote.positive.destroy_by(post: post, user: user)
|
||||
vote = PostVote.active.positive.find_by(post: post, user: user)
|
||||
vote&.soft_delete!(updater: user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,6 +35,8 @@ class ModAction < ApplicationRecord
|
||||
post_note_lock_delete: 212,
|
||||
post_rating_lock_create: 220,
|
||||
post_rating_lock_delete: 222,
|
||||
post_vote_delete: 232,
|
||||
post_vote_undelete: 233,
|
||||
pool_delete: 62,
|
||||
pool_undelete: 63,
|
||||
artist_ban: 184,
|
||||
|
||||
@@ -40,7 +40,7 @@ class Post < ApplicationRecord
|
||||
has_one :upload, :dependent => :destroy
|
||||
has_one :artist_commentary, :dependent => :destroy
|
||||
has_one :pixiv_ugoira_frame_data, class_name: "PixivUgoiraFrameData", foreign_key: :md5, primary_key: :md5
|
||||
has_one :vote_by_current_user, -> { where(user_id: CurrentUser.id) }, class_name: "PostVote" # XXX using current user here is wrong
|
||||
has_one :vote_by_current_user, -> { active.where(user_id: CurrentUser.id) }, class_name: "PostVote" # XXX using current user here is wrong
|
||||
has_many :flags, :class_name => "PostFlag", :dependent => :destroy
|
||||
has_many :appeals, :class_name => "PostAppeal", :dependent => :destroy
|
||||
has_many :votes, :class_name => "PostVote", :dependent => :destroy
|
||||
@@ -701,18 +701,10 @@ class Post < ApplicationRecord
|
||||
return unless Pundit.policy!(voter, PostVote).create?
|
||||
|
||||
with_lock do
|
||||
votes.destroy_by(user: voter)
|
||||
votes.create!(user: voter, score: score)
|
||||
votes.create!(user: voter, score: score) unless votes.active.exists?(user: voter, score: score)
|
||||
reload # PostVote.create modifies our score. Reload to get the new score.
|
||||
end
|
||||
end
|
||||
|
||||
def unvote!(voter)
|
||||
return unless Pundit.policy!(voter, PostVote).create?
|
||||
|
||||
votes.destroy_by(user: voter)
|
||||
reload
|
||||
end
|
||||
end
|
||||
|
||||
module ParentMethods
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
class PostVote < ApplicationRecord
|
||||
attr_accessor :updater
|
||||
|
||||
belongs_to :post
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, uniqueness: { scope: :post_id, message: "have already voted for this post" }
|
||||
validates :score, inclusion: { in: [1, -1], message: "must be 1 or -1" }
|
||||
|
||||
after_create :update_score_after_create
|
||||
after_destroy :update_score_after_destroy
|
||||
before_save { post.lock! }
|
||||
before_save :update_score_on_delete, if: -> { !new_record? && is_deleted_changed?(from: false, to: true) }
|
||||
before_save :update_score_on_undelete, if: -> { !new_record? && is_deleted_changed?(from: true, to: false) }
|
||||
before_save :create_mod_action_on_delete_or_undelete
|
||||
before_create :update_score_on_create
|
||||
before_create :remove_conflicting_votes
|
||||
|
||||
scope :positive, -> { where("post_votes.score > 0") }
|
||||
scope :negative, -> { where("post_votes.score < 0") }
|
||||
scope :public_votes, -> { positive.where(user: User.has_public_favorites) }
|
||||
scope :public_votes, -> { active.positive.where(user: User.has_public_favorites) }
|
||||
|
||||
deletable
|
||||
|
||||
def self.visible(user)
|
||||
user.is_admin? ? all : where(user: user).or(public_votes)
|
||||
if user.is_admin?
|
||||
all
|
||||
elsif user.is_anonymous?
|
||||
public_votes
|
||||
else
|
||||
active.where(user: user).or(public_votes)
|
||||
end
|
||||
end
|
||||
|
||||
def self.search(params)
|
||||
q = search_attributes(params, :id, :created_at, :updated_at, :score, :user, :post)
|
||||
q = search_attributes(params, :id, :created_at, :updated_at, :score, :is_deleted, :user, :post)
|
||||
|
||||
q.apply_default_order(params)
|
||||
end
|
||||
@@ -32,7 +43,19 @@ class PostVote < ApplicationRecord
|
||||
score < 0
|
||||
end
|
||||
|
||||
def update_score_after_create
|
||||
def remove_conflicting_votes
|
||||
PostVote.active.where.not(id: id).where(post: post, user: user).each do |vote|
|
||||
vote.soft_delete!(updater: updater)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_vote_is_unique
|
||||
if !is_deleted? && PostVote.active.where.not(id: id).exists?(post: post, user: user)
|
||||
errors.add(:user, "have already voted for this post")
|
||||
end
|
||||
end
|
||||
|
||||
def update_score_on_create
|
||||
if is_positive?
|
||||
Post.update_counters(post_id, { score: score, up_score: score })
|
||||
else
|
||||
@@ -40,7 +63,7 @@ class PostVote < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def update_score_after_destroy
|
||||
def update_score_on_delete
|
||||
if is_positive?
|
||||
Post.update_counters(post_id, { score: -score, up_score: -score })
|
||||
else
|
||||
@@ -48,6 +71,20 @@ class PostVote < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def update_score_on_undelete
|
||||
update_score_on_create
|
||||
end
|
||||
|
||||
def create_mod_action_on_delete_or_undelete
|
||||
return if new_record? || updater.nil? || updater == user
|
||||
|
||||
if is_deleted_changed?(from: false, to: true)
|
||||
ModAction.log("#{updater.name} deleted post vote ##{id} on post ##{post_id}", :post_vote_delete, updater)
|
||||
elsif is_deleted_changed?(from: true, to: false)
|
||||
ModAction.log("#{updater.name} undeleted post vote ##{id} on post ##{post_id}", :post_vote_undelete, updater)
|
||||
end
|
||||
end
|
||||
|
||||
def self.available_includes
|
||||
[:user, :post]
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user