discourse/app/models/queued_post.rb

153 lines
4.0 KiB
Ruby
Raw Normal View History

class QueuedPost < ActiveRecord::Base
class InvalidStateTransition < StandardError; end
belongs_to :user
belongs_to :topic
belongs_to :approved_by, class_name: "User"
belongs_to :rejected_by, class_name: "User"
after_commit :trigger_queued_post_event, on: :create
def create_pending_action
UserAction.log_action!(action_type: UserAction::PENDING,
user_id: user_id,
acting_user_id: user_id,
target_topic_id: topic_id,
queued_post_id: id)
end
def trigger_queued_post_event
DiscourseEvent.trigger(:queued_post_created, self)
true
end
def self.states
@states ||= Enum.new(:new, :approved, :rejected)
end
# By default queues are hidden from moderators
def self.visible_queues
@visible_queues ||= Set.new(['default'])
end
def self.visible
where(queue: visible_queues.to_a)
end
def self.new_posts
where(state: states[:new])
end
def self.new_count
new_posts.visible.count
end
def visible?
QueuedPost.visible_queues.include?(queue)
end
2015-04-11 05:00:50 +08:00
def self.broadcast_new!
msg = { post_queue_new_count: QueuedPost.new_count }
MessageBus.publish('/queue_counts', msg, user_ids: User.staff.pluck(:id))
end
def reject!(rejected_by)
change_to!(:rejected, rejected_by)
StaffActionLogger.new(rejected_by).log_post_rejected(self)
DiscourseEvent.trigger(:rejected_post, self)
end
def create_options
2017-07-28 09:20:09 +08:00
opts = { raw: raw }
opts.merge!(post_options.symbolize_keys)
opts[:cooking_options].symbolize_keys! if opts[:cooking_options]
opts[:topic_id] = topic_id if topic_id
opts
end
def approve!(approved_by)
created_post = nil
creator = PostCreator.new(user, create_options.merge(
skip_validations: true,
skip_jobs: true,
skip_events: true
))
QueuedPost.transaction do
change_to!(:approved, approved_by)
2017-11-11 01:18:08 +08:00
UserSilencer.unsilence(user, approved_by) if user.silenced?
2015-08-05 08:49:59 +08:00
created_post = creator.create
unless created_post && creator.errors.blank?
raise StandardError.new(creator.errors.full_messages.join(" "))
else
# Log post approval
StaffActionLogger.new(approved_by).log_post_approved(created_post)
end
end
# Do sidekiq work outside of the transaction
creator.enqueue_jobs
creator.trigger_after_events
DiscourseEvent.trigger(:approved_post, self, created_post)
created_post
end
private
2018-06-07 13:28:18 +08:00
def change_to!(state, changed_by)
state_val = QueuedPost.states[state]
2018-06-07 13:28:18 +08:00
updates = { state: state_val,
"#{state}_by_id" => changed_by.id,
"#{state}_at" => Time.now }
2018-06-07 13:28:18 +08:00
# We use an update with `row_count` trick here to avoid stampeding requests to
# update the same row simultaneously. Only one state change should go through and
# we can use the DB to enforce this
row_count = QueuedPost.where('id = ? AND state <> ?', id, state_val).update_all(updates)
raise InvalidStateTransition.new if row_count == 0
2018-06-07 13:28:18 +08:00
if [:rejected, :approved].include?(state)
UserAction.where(queued_post_id: id).destroy_all
end
2018-06-07 13:28:18 +08:00
# Update the record in memory too, and clear the dirty flag
updates.each { |k, v| send("#{k}=", v) }
changes_applied
2018-06-07 13:28:18 +08:00
QueuedPost.broadcast_new! if visible?
end
end
2015-09-18 08:41:10 +08:00
# == Schema Information
#
# Table name: queued_posts
#
# id :integer not null, primary key
2019-01-12 03:29:56 +08:00
# queue :string not null
2015-09-18 08:41:10 +08:00
# state :integer not null
# user_id :integer not null
# raw :text not null
# post_options :json not null
# topic_id :integer
# approved_by_id :integer
# approved_at :datetime
# rejected_by_id :integer
# rejected_at :datetime
2019-01-12 03:29:56 +08:00
# created_at :datetime not null
# updated_at :datetime not null
2015-09-18 08:41:10 +08:00
#
# Indexes
#
# by_queue_status (queue,state,created_at)
# by_queue_status_topic (topic_id,queue,state,created_at)
#