Fix #4568: Send appealed posts back to the mod queue
* Include appealed posts in the modqueue. * Add `status` field to appeals. Appeals start out as `pending`, then become `rejected` if the post isn't approved within three days. If the post is approved, the appeal's status becomes `succeeded`. * Add `status` field to flags. Flags start out as `pending` then become `rejected` if the post is approved within three days. If the post isn't approved, the flag's status becomes `succeeded`. * Leave behind a "Unapproved in three days" dummy flag when an appeal goes unapproved, just like when a pending post is unapproved. * Only allow deleted posts to be appealed. Don't allow flagged posts to be appealed. * Add `status:appealed` metatag. `status:appealed` is separate from `status:pending`. * Include appealed posts in `status:modqueue`. Search `status:modqueue order:modqueue` to view the modqueue as a normal search. * Retroactively set old flags and appeals as succeeded or rejected. This may not be correct for posts that were appealed or flagged multiple times. This is difficult to set correctly because we don't have approval records for old posts, so we can't tell the actual outcome of old flags and appeals. * Deprecate the `is_resolved` field on post flags. A resolved flag is a flag that isn't pending. * Known bug: appealed posts have a black border instead of a blue border. Checking whether a post has been appealed would require either an extra query on the posts/index page, or an is_appealed flag on posts, neither of which are very desirable. * Known bug: you can't use `status:appealed` in blacklists, for the same reason as above.
This commit is contained in:
@@ -111,6 +111,10 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
ensure
|
||||
connection.execute("SET STATEMENT_TIMEOUT = #{CurrentUser.user.try(:statement_timeout) || 3_000}") unless Rails.env == "test"
|
||||
end
|
||||
|
||||
def update!(*args)
|
||||
all.each { |record| record.update!(*args) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@ class Post < ApplicationRecord
|
||||
scope :pending, -> { where(is_pending: true) }
|
||||
scope :flagged, -> { where(is_flagged: true) }
|
||||
scope :banned, -> { where(is_banned: true) }
|
||||
scope :active, -> { where(is_pending: false, is_deleted: false, is_flagged: false) }
|
||||
scope :pending_or_flagged, -> { pending.or(flagged) }
|
||||
scope :active, -> { where(is_pending: false, is_deleted: false, is_flagged: false).where.not(id: PostAppeal.pending) }
|
||||
scope :appealed, -> { where(id: PostAppeal.pending.select(:post_id)) }
|
||||
scope :in_modqueue, -> { pending.or(flagged).or(appealed) }
|
||||
scope :expired, -> { where("posts.created_at < ?", 3.days.ago) }
|
||||
|
||||
scope :unflagged, -> { where(is_flagged: false) }
|
||||
@@ -281,8 +282,24 @@ class Post < ApplicationRecord
|
||||
end
|
||||
|
||||
module ApprovalMethods
|
||||
def in_modqueue?
|
||||
is_pending? || is_flagged? || is_appealed?
|
||||
end
|
||||
|
||||
def is_active?
|
||||
!is_deleted? && !in_modqueue?
|
||||
end
|
||||
|
||||
def is_appealed?
|
||||
is_deleted? && appeals.any?(&:pending?)
|
||||
end
|
||||
|
||||
def is_appealable?
|
||||
is_deleted? && !is_appealed?
|
||||
end
|
||||
|
||||
def is_approvable?(user = CurrentUser.user)
|
||||
!is_status_locked? && (is_pending? || is_flagged? || is_deleted?) && uploader != user
|
||||
!is_status_locked? && !is_active? && uploader != user
|
||||
end
|
||||
|
||||
def flag!(reason, is_deletion: false)
|
||||
@@ -991,7 +1008,10 @@ class Post < ApplicationRecord
|
||||
transaction do
|
||||
automated = (user == User.system)
|
||||
|
||||
flags.create!(reason: reason, is_deletion: true, creator: user)
|
||||
flags.pending.update!(status: :succeeded)
|
||||
appeals.pending.update!(status: :rejected)
|
||||
|
||||
flags.create!(reason: reason, is_deletion: true, creator: user, status: :succeeded)
|
||||
update!(is_deleted: true, is_pending: false, is_flagged: false)
|
||||
|
||||
# XXX This must happen *after* the `is_deleted` flag is set to true (issue #3419).
|
||||
@@ -1224,6 +1244,14 @@ class Post < ApplicationRecord
|
||||
relation
|
||||
end
|
||||
|
||||
def with_queued_at
|
||||
relation = group(:id)
|
||||
relation = relation.left_outer_joins(:flags, :appeals)
|
||||
relation = relation.select("posts.*")
|
||||
relation = relation.select(Arel.sql("MAX(GREATEST(posts.created_at, post_flags.created_at, post_appeals.created_at)) AS queued_at"))
|
||||
relation
|
||||
end
|
||||
|
||||
def with_stats(tables)
|
||||
return all if tables.empty?
|
||||
|
||||
|
||||
@@ -5,20 +5,27 @@ class PostAppeal < ApplicationRecord
|
||||
|
||||
belongs_to :creator, :class_name => "User"
|
||||
belongs_to :post
|
||||
validates_presence_of :reason
|
||||
|
||||
validates :reason, presence: true, length: { in: 1..140 }
|
||||
validate :validate_post_is_inactive
|
||||
validate :validate_creator_is_not_limited
|
||||
validates_uniqueness_of :creator_id, :scope => :post_id, :message => "have already appealed this post"
|
||||
validate :validate_post_is_appealable, on: :create
|
||||
validate :validate_creator_is_not_limited, on: :create
|
||||
validates :creator, uniqueness: { scope: :post, message: "have already appealed this post" }, on: :create
|
||||
|
||||
enum status: {
|
||||
pending: 0,
|
||||
succeeded: 1,
|
||||
rejected: 2
|
||||
}
|
||||
|
||||
scope :resolved, -> { where(post: Post.undeleted.unflagged) }
|
||||
scope :unresolved, -> { where(post: Post.deleted.or(Post.flagged)) }
|
||||
scope :recent, -> { where("post_appeals.created_at >= ?", 1.day.ago) }
|
||||
scope :expired, -> { pending.where("post_appeals.created_at <= ?", 3.days.ago) }
|
||||
|
||||
module SearchMethods
|
||||
def search(params)
|
||||
q = super
|
||||
q = q.search_attributes(params, :creator, :post, :reason)
|
||||
q = q.search_attributes(params, :creator, :post, :reason, :status)
|
||||
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
||||
|
||||
q = q.resolved if params[:is_resolved].to_s.truthy?
|
||||
@@ -44,10 +51,8 @@ class PostAppeal < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def validate_post_is_inactive
|
||||
if resolved?
|
||||
errors[:post] << "is active"
|
||||
end
|
||||
def validate_post_is_appealable
|
||||
errors[:post] << "cannot be appealed" if post.is_status_locked? || !post.is_appealable?
|
||||
end
|
||||
|
||||
def appeal_count_for_creator
|
||||
|
||||
@@ -12,7 +12,7 @@ class PostApproval < ApplicationRecord
|
||||
errors.add(:post, "is locked and cannot be approved")
|
||||
end
|
||||
|
||||
if post.status == "active"
|
||||
if post.is_active?
|
||||
errors.add(:post, "is already active and cannot be approved")
|
||||
end
|
||||
|
||||
@@ -28,7 +28,9 @@ class PostApproval < ApplicationRecord
|
||||
def approve_post
|
||||
is_undeletion = post.is_deleted
|
||||
|
||||
post.flags.each(&:resolve!)
|
||||
post.flags.pending.update!(status: :rejected)
|
||||
post.appeals.pending.update!(status: :succeeded)
|
||||
|
||||
post.update(approver: user, is_flagged: false, is_pending: false, is_deleted: false)
|
||||
ModAction.log("undeleted post ##{post_id}", :post_undelete) if is_undeletion
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class PostDisapproval < ApplicationRecord
|
||||
end
|
||||
|
||||
def validate_disapproval
|
||||
if post.status == "active"
|
||||
if post.is_active?
|
||||
errors[:post] << "is already active and cannot be disapproved"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,13 +17,19 @@ class PostFlag < ApplicationRecord
|
||||
before_save :update_post
|
||||
attr_accessor :is_deletion
|
||||
|
||||
enum status: {
|
||||
pending: 0,
|
||||
succeeded: 1,
|
||||
rejected: 2
|
||||
}
|
||||
|
||||
scope :by_users, -> { where.not(creator: User.system) }
|
||||
scope :by_system, -> { where(creator: User.system) }
|
||||
scope :in_cooldown, -> { by_users.where("created_at >= ?", COOLDOWN_PERIOD.ago) }
|
||||
scope :resolved, -> { where(is_resolved: true) }
|
||||
scope :unresolved, -> { where(is_resolved: false) }
|
||||
scope :recent, -> { where("post_flags.created_at >= ?", 1.day.ago) }
|
||||
scope :old, -> { where("post_flags.created_at <= ?", 3.days.ago) }
|
||||
scope :expired, -> { pending.where("post_flags.created_at <= ?", 3.days.ago) }
|
||||
|
||||
module SearchMethods
|
||||
def creator_matches(creator, searcher)
|
||||
@@ -56,7 +62,7 @@ class PostFlag < ApplicationRecord
|
||||
def search(params)
|
||||
q = super
|
||||
|
||||
q = q.search_attributes(params, :post, :is_resolved, :reason)
|
||||
q = q.search_attributes(params, :post, :is_resolved, :reason, :status)
|
||||
q = q.text_attribute_matches(:reason, params[:reason_matches])
|
||||
|
||||
if params[:creator_id].present?
|
||||
@@ -113,12 +119,8 @@ class PostFlag < ApplicationRecord
|
||||
|
||||
def validate_post
|
||||
errors[:post] << "is pending and cannot be flagged" if post.is_pending? && !is_deletion
|
||||
errors[:post] << "is deleted and cannot be flagged" if post.is_deleted? && !is_deletion
|
||||
errors[:post] << "is locked and cannot be flagged" if post.is_status_locked?
|
||||
errors[:post] << "is deleted" if post.is_deleted?
|
||||
end
|
||||
|
||||
def resolve!
|
||||
update_column(:is_resolved, true)
|
||||
end
|
||||
|
||||
def flag_count_for_creator
|
||||
|
||||
Reference in New Issue
Block a user