Files
danbooru/app/models/bulk_update_request.rb
evazion 2bffad9311 BURs: credit tag edits to DanbooruBot.
Credit tag edits that are performed as part of an alias, implication, or
mass update to DanbooruBot instead of the admin who approved the BUR.
2019-12-06 22:54:08 -06:00

224 lines
6.1 KiB
Ruby

class BulkUpdateRequest < ApplicationRecord
attr_accessor :reason, :skip_secondary_validations
belongs_to :user
belongs_to :forum_topic, optional: true
belongs_to :forum_post, optional: true
belongs_to :approver, optional: true, class_name: "User"
validates_presence_of :script
validates_presence_of :title, if: ->(rec) {rec.forum_topic_id.blank?}
validates_inclusion_of :status, :in => %w(pending approved rejected)
validate :script_formatted_correctly
validate :forum_topic_id_not_invalid
validate :validate_script, :on => :create
before_validation :initialize_attributes, :on => :create
before_validation :normalize_text
after_create :create_forum_topic
after_save :update_notice
scope :pending_first, -> { order(Arel.sql("(case status when 'pending' then 0 when 'approved' then 1 else 2 end)")) }
scope :pending, -> {where(status: "pending")}
scope :expired, -> {where("created_at < ?", TagRelationship::EXPIRY.days.ago)}
scope :old, -> {where("created_at between ? and ?", TagRelationship::EXPIRY.days.ago, TagRelationship::EXPIRY_WARNING.days.ago)}
module SearchMethods
def default_order
pending_first.order(id: :desc)
end
def search(params = {})
q = super
q = q.search_attributes(params, :user, :approver, :forum_topic_id, :forum_post_id, :title, :script)
q = q.text_attribute_matches(:title, params[:title_matches])
q = q.text_attribute_matches(:script, params[:script_matches])
if params[:status].present?
q = q.where(status: params[:status].split(","))
end
params[:order] ||= "status_desc"
case params[:order]
when "id_desc"
q = q.order(id: :desc)
when "id_asc"
q = q.order(id: :asc)
when "updated_at_desc"
q = q.order(updated_at: :desc)
when "updated_at_asc"
q = q.order(updated_at: :asc)
else
q = q.apply_default_order(params)
end
q
end
end
module ApprovalMethods
def forum_updater
@forum_updater ||= begin
post = if forum_topic
forum_post || forum_topic.posts.first
else
nil
end
ForumUpdater.new(
forum_topic,
forum_post: post,
expected_title: title,
skip_update: !TagRelationship::SUPPORT_HARD_CODED
)
end
end
def approve!(approver)
transaction do
CurrentUser.scoped(approver) do
AliasAndImplicationImporter.new(script, forum_topic_id, "1", true).process!
update!(status: "approved", approver: approver, skip_secondary_validations: true)
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has been approved by @#{approver.name}.", "APPROVED")
end
end
rescue AliasAndImplicationImporter::Error => x
self.approver = approver
CurrentUser.scoped(approver) do
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has failed: #{x.to_s}", "FAILED")
end
end
def date_timestamp
Time.now.strftime("%Y-%m-%d")
end
def create_forum_topic
if forum_topic_id
forum_post = forum_topic.posts.create(body: reason_with_link)
update(forum_post_id: forum_post.id)
else
forum_topic = ForumTopic.create(title: title, category_id: 1, original_post_attributes: {body: reason_with_link})
update(forum_topic_id: forum_topic.id, forum_post_id: forum_topic.posts.first.id)
end
end
def reject!(rejector = User.system)
transaction do
update(status: "rejected")
forum_updater.update("The #{bulk_update_request_link} (forum ##{forum_post.id}) has been rejected by @#{rejector.name}.", "REJECTED")
end
end
def bulk_update_request_link
%{"bulk update request ##{id}":/bulk_update_requests?search%5Bid%5D=#{id}}
end
end
module ValidationMethods
def script_formatted_correctly
AliasAndImplicationImporter.tokenize(script)
rescue StandardError => e
errors[:base] << e.message
end
def forum_topic_id_not_invalid
if forum_topic_id && !forum_topic
errors[:base] << "Forum topic ID is invalid"
end
end
def validate_script
begin
AliasAndImplicationImporter.new(script, forum_topic_id, "1", skip_secondary_validations).validate!
rescue RuntimeError => e
errors[:base] << e.message
end
end
end
extend SearchMethods
include ApprovalMethods
include ValidationMethods
concerning :EmbeddedText do
class_methods do
def embedded_pattern
/\[bur:(?<id>\d+)\]/m
end
end
end
def editable?(user)
user_id == user.id || user.is_builder?
end
def approvable?(user)
!is_approved? && user.is_admin?
end
def rejectable?(user)
is_pending? && editable?(user)
end
def reason_with_link
"[bur:#{id}]\n\nReason: #{reason}"
end
def script_with_links
tokens = AliasAndImplicationImporter.tokenize(script)
lines = tokens.map do |token|
case token[0]
when :create_alias, :create_implication, :remove_alias, :remove_implication
"#{token[0].to_s.tr("_", " ")} [[#{token[1]}]] -> [[#{token[2]}]]"
when :mass_update
"mass update {{#{token[1]}}} -> #{token[2]}"
when :change_category
"category [[#{token[1]}]] -> #{token[2]}"
else
raise "Unknown token: #{token[0]}"
end
end
lines.join("\n")
end
def initialize_attributes
self.user_id = CurrentUser.user.id unless self.user_id
self.status = "pending"
end
def normalize_text
self.script = script.downcase
end
def skip_secondary_validations=(v)
@skip_secondary_validations = v.to_s.truthy?
end
def is_pending?
status == "pending"
end
def is_approved?
status == "approved"
end
def is_rejected?
status == "rejected"
end
def estimate_update_count
AliasAndImplicationImporter.new(script, nil).estimate_update_count
end
def update_notice
TagChangeNoticeService.update_cache(
AliasAndImplicationImporter.new(script, nil).affected_tags,
forum_topic_id
)
end
end