Files
danbooru/app/models/application_record.rb
evazion 0a0a85ee70 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.
2020-08-06 20:55:45 -05:00

184 lines
5.4 KiB
Ruby

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include Deletable
include Mentionable
extend HasBitFlags
extend Searchable
concerning :PaginationMethods do
class_methods do
def paginate(*args, **options)
extending(PaginationExtension).paginate(*args, **options)
end
def paginated_search(params, count_pages: params[:search].present?, **defaults)
search_params = params.fetch(:search, {}).permit!
search_params = defaults.merge(search_params).with_indifferent_access
max_limit = (params[:format] == "sitemap") ? 10_000 : 1_000
search(search_params).paginate(params[:page], limit: params[:limit], max_limit: max_limit, search_count: count_pages)
end
end
end
concerning :PrivilegeMethods do
class_methods do
def visible(user)
all
end
end
end
concerning :ApiMethods do
class_methods do
def available_includes
[]
end
def multiple_includes
reflections.reject { |k,v| v.macro != :has_many }.keys.map(&:to_sym)
end
def associated_models(name)
if reflections[name].options[:polymorphic]
associated_models = reflections[name].active_record.try(:model_types) || []
else
associated_models = [reflections[name].class_name]
end
end
end
def available_includes
self.class.available_includes
end
# XXX deprecated, shouldn't expose this as an instance method.
def api_attributes(user: CurrentUser.user)
policy = Pundit.policy([user, nil], self) || ApplicationPolicy.new([user, nil], self)
policy.api_attributes
end
def html_data_attributes
data_attributes = self.class.columns.select do |column|
column.type.in?([:integer, :boolean]) && !column.array?
end.map(&:name).map(&:to_sym)
api_attributes & data_attributes
end
def serializable_hash(options = {})
options ||= {}
if options[:only] && options[:only].is_a?(String)
options.delete(:methods)
options.delete(:include)
options.merge!(ParameterBuilder.serial_parameters(options[:only], self))
else
options[:methods] ||= []
attributes, methods = api_attributes.partition { |attr| has_attribute?(attr) }
methods += options[:methods]
options[:only] ||= attributes + methods
attributes &= options[:only]
methods &= options[:only]
options[:only] = attributes
options[:methods] = methods
options.delete(:methods) if options[:methods].empty?
end
hash = super(options)
hash.transform_keys { |key| key.delete("?") }
end
end
concerning :ActiveRecordExtensions do
class_methods do
def without_timeout
connection.execute("SET STATEMENT_TIMEOUT = 0") unless Rails.env == "test"
yield
ensure
connection.execute("SET STATEMENT_TIMEOUT = #{CurrentUser.user.try(:statement_timeout) || 3_000}") unless Rails.env == "test"
end
def with_timeout(n, default_value = nil, new_relic_params = {})
connection.execute("SET STATEMENT_TIMEOUT = #{n}") unless Rails.env == "test"
yield
rescue ::ActiveRecord::StatementInvalid => x
DanbooruLogger.log(x, expected: false, **new_relic_params)
return default_value
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
concerning :PostgresExtensions do
class_methods do
def columns(*params)
super.reject {|x| x.sql_type == "tsvector"}
end
end
end
concerning :UserMethods do
class_methods do
def belongs_to_updater(**options)
class_eval do
belongs_to :updater, class_name: "User", **options
before_validation do |rec|
rec.updater_id = CurrentUser.id
rec.updater_ip_addr = CurrentUser.ip_addr if rec.respond_to?(:updater_ip_addr=)
end
end
end
end
end
concerning :DtextMethods do
def dtext_shortlink(**options)
"#{self.class.name.underscore.tr("_", " ")} ##{id}"
end
end
concerning :AttributeMethods do
class_methods do
# Defines `<attribute>_string`, `<attribute>_string=`, and `<attribute>=`
# methods for converting an array attribute to or from a string.
#
# The `<attribute>=` setter parses strings into an array using the
# `parse` regex. The resulting strings can be converted to another type
# with the `cast` option.
def array_attribute(name, parse: /[^[:space:]]+/, cast: :itself)
define_method "#{name}_string" do
send(name).join(" ")
end
define_method "#{name}_string=" do |value|
raise ArgumentError, "#{name} must be a String" unless value.respond_to?(:to_str)
send("#{name}=", value)
end
define_method "#{name}=" do |value|
if value.respond_to?(:to_str)
super value.to_str.scan(parse).map(&cast)
elsif value.respond_to?(:to_a)
super value.to_a
else
raise ArgumentError, "#{name} must be a String or an Array"
end
end
end
end
end
def warnings
@warnings ||= ActiveModel::Errors.new(self)
end
end