mirror of
https://github.com/discourse/discourse.git
synced 2025-02-10 05:14:59 +00:00
PostEnqueuer
object to handle validation of enqueued posts
This commit is contained in:
parent
8ba6a45cd7
commit
a5ee45ccbe
@ -369,7 +369,9 @@ class Post < ActiveRecord::Base
|
|||||||
problems
|
problems
|
||||||
end
|
end
|
||||||
|
|
||||||
def rebake!(opts={})
|
def rebake!(opts=nil)
|
||||||
|
opts ||= {}
|
||||||
|
|
||||||
new_cooked = cook(
|
new_cooked = cook(
|
||||||
raw,
|
raw,
|
||||||
topic_id: topic_id,
|
topic_id: topic_id,
|
||||||
|
@ -2,8 +2,6 @@ class QueuedPost < ActiveRecord::Base
|
|||||||
|
|
||||||
class InvalidStateTransition < StandardError; end;
|
class InvalidStateTransition < StandardError; end;
|
||||||
|
|
||||||
serialize :post_options, JSON
|
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :topic
|
belongs_to :topic
|
||||||
belongs_to :approved_by, class_name: "User"
|
belongs_to :approved_by, class_name: "User"
|
||||||
@ -36,6 +34,8 @@ class QueuedPost < ActiveRecord::Base
|
|||||||
opts = {raw: raw}
|
opts = {raw: raw}
|
||||||
post_attributes.each {|a| opts[a] = post_options[a.to_s] }
|
post_attributes.each {|a| opts[a] = post_options[a.to_s] }
|
||||||
|
|
||||||
|
opts[:cooking_options].symbolize_keys! if opts[:cooking_options]
|
||||||
|
|
||||||
opts[:topic_id] = topic_id if topic_id
|
opts[:topic_id] = topic_id if topic_id
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
@ -52,6 +52,7 @@ class QueuedPost < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def post_attributes
|
def post_attributes
|
||||||
[QueuedPost.attributes_by_queue[:base], QueuedPost.attributes_by_queue[queue.to_sym]].flatten.compact
|
[QueuedPost.attributes_by_queue[:base], QueuedPost.attributes_by_queue[queue.to_sym]].flatten.compact
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
class CreateDirectoryItems < ActiveRecord::Migration
|
class CreateDirectoryItems < ActiveRecord::Migration
|
||||||
def change
|
def change
|
||||||
create_table :directory_items do |t|
|
create_table :directory_items, force: true do |t|
|
||||||
t.integer :period_type, null: false
|
t.integer :period_type, null: false
|
||||||
t.references :user, null: false
|
t.references :user, null: false
|
||||||
t.integer :likes_received, null: false
|
t.integer :likes_received, null: false
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
class AddMutedUsers < ActiveRecord::Migration
|
class AddMutedUsers < ActiveRecord::Migration
|
||||||
def change
|
def change
|
||||||
create_table :muted_users do |t|
|
create_table :muted_users, force: true do |t|
|
||||||
t.integer :user_id, null: false
|
t.integer :user_id, null: false
|
||||||
t.integer :muted_user_id, null: false
|
t.integer :muted_user_id, null: false
|
||||||
t.timestamps
|
t.timestamps
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
class CreateQueuedPosts < ActiveRecord::Migration
|
class CreateQueuedPosts < ActiveRecord::Migration
|
||||||
def change
|
def change
|
||||||
create_table :queued_posts do |t|
|
create_table :queued_posts, force: true do |t|
|
||||||
t.string :queue, null: false
|
t.string :queue, null: false
|
||||||
t.integer :state, null: false
|
t.integer :state, null: false
|
||||||
t.integer :user_id, null: false
|
t.integer :user_id, null: false
|
||||||
t.text :raw, null: false
|
t.text :raw, null: false
|
||||||
t.text :post_options, null: false
|
t.json :post_options, null: false
|
||||||
t.integer :topic_id
|
t.integer :topic_id
|
||||||
t.integer :approved_by_id
|
t.integer :approved_by_id
|
||||||
t.timestamp :approved_at
|
t.timestamp :approved_at
|
||||||
@ -15,6 +15,6 @@ class CreateQueuedPosts < ActiveRecord::Migration
|
|||||||
end
|
end
|
||||||
|
|
||||||
add_index :queued_posts, [:queue, :state, :created_at], name: 'by_queue_status'
|
add_index :queued_posts, [:queue, :state, :created_at], name: 'by_queue_status'
|
||||||
add_index :queued_posts, [:topic, :queue, :state, :created_at], name: 'by_queue_status_topic'
|
add_index :queued_posts, [:topic_id, :queue, :state, :created_at], name: 'by_queue_status_topic'
|
||||||
end
|
end
|
||||||
end
|
end
|
31
lib/has_errors.rb
Normal file
31
lib/has_errors.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Helper functions for dealing with errors and objects that have
|
||||||
|
# child objects with errors
|
||||||
|
module HasErrors
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@errors ||= ActiveModel::Errors.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_child(obj)
|
||||||
|
return true if obj.valid?
|
||||||
|
add_errors_from(obj)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback_with!(obj, error)
|
||||||
|
obj.errors[:base] << error
|
||||||
|
rollback_from_errors!(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback_from_errors!(obj)
|
||||||
|
add_errors_from(obj)
|
||||||
|
raise ActiveRecord::Rollback.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_errors_from(obj)
|
||||||
|
obj.errors.full_messages.each do |msg|
|
||||||
|
errors[:base] << msg unless errors[:base].include?(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -4,10 +4,12 @@ require_dependency 'rate_limiter'
|
|||||||
require_dependency 'topic_creator'
|
require_dependency 'topic_creator'
|
||||||
require_dependency 'post_jobs_enqueuer'
|
require_dependency 'post_jobs_enqueuer'
|
||||||
require_dependency 'distributed_mutex'
|
require_dependency 'distributed_mutex'
|
||||||
|
require_dependency 'has_errors'
|
||||||
|
|
||||||
class PostCreator
|
class PostCreator
|
||||||
|
include HasErrors
|
||||||
|
|
||||||
attr_reader :errors, :opts
|
attr_reader :opts
|
||||||
|
|
||||||
# Acceptable options:
|
# Acceptable options:
|
||||||
#
|
#
|
||||||
@ -66,36 +68,66 @@ class PostCreator
|
|||||||
@guardian ||= Guardian.new(@user)
|
@guardian ||= Guardian.new(@user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def valid?
|
||||||
@topic = nil
|
@topic = nil
|
||||||
@post = nil
|
@post = nil
|
||||||
|
|
||||||
if @user.suspended? && !skip_validations?
|
if @user.suspended? && !skip_validations?
|
||||||
@errors = Post.new.errors
|
errors[:base] << I18n.t(:user_is_suspended)
|
||||||
@errors.add(:base, I18n.t(:user_is_suspended))
|
return false
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
transaction do
|
if new_topic?
|
||||||
setup_topic
|
topic_creator = TopicCreator.new(@user, guardian, @opts)
|
||||||
setup_post
|
return false unless skip_validations? || validate_child(topic_creator)
|
||||||
rollback_if_host_spam_detected
|
else
|
||||||
plugin_callbacks
|
@topic = Topic.find_by(id: @opts[:topic_id])
|
||||||
save_post
|
if (@topic.blank? || !guardian.can_create?(Post, @topic))
|
||||||
extract_links
|
errors[:base] << I18n.t(:topic_not_found)
|
||||||
store_unique_post_key
|
return false
|
||||||
track_topic
|
end
|
||||||
update_topic_stats
|
|
||||||
update_topic_auto_close
|
|
||||||
update_user_counts
|
|
||||||
create_embedded_topic
|
|
||||||
|
|
||||||
ensure_in_allowed_users if guardian.is_staff?
|
|
||||||
@post.advance_draft_sequence
|
|
||||||
@post.save_reply_relationships
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if @post && @post.errors.empty?
|
setup_post
|
||||||
|
|
||||||
|
return true if skip_validations?
|
||||||
|
if @post.has_host_spam?
|
||||||
|
@spam = true
|
||||||
|
errors[:base] << I18n.t(:spamming_host)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
DiscourseEvent.trigger :before_create_post, @post
|
||||||
|
DiscourseEvent.trigger :validate_post, @post
|
||||||
|
|
||||||
|
post_validator = Validators::PostValidator.new(skip_topic: true)
|
||||||
|
post_validator.validate(@post)
|
||||||
|
|
||||||
|
valid = @post.errors.blank?
|
||||||
|
add_errors_from(@post) unless valid
|
||||||
|
valid
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
if valid?
|
||||||
|
transaction do
|
||||||
|
create_topic
|
||||||
|
save_post
|
||||||
|
extract_links
|
||||||
|
store_unique_post_key
|
||||||
|
track_topic
|
||||||
|
update_topic_stats
|
||||||
|
update_topic_auto_close
|
||||||
|
update_user_counts
|
||||||
|
create_embedded_topic
|
||||||
|
|
||||||
|
ensure_in_allowed_users if guardian.is_staff?
|
||||||
|
@post.advance_draft_sequence
|
||||||
|
@post.save_reply_relationships
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if @post && errors.blank?
|
||||||
publish
|
publish
|
||||||
PostAlerter.post_created(@post) unless @opts[:import_mode]
|
PostAlerter.post_created(@post) unless @opts[:import_mode]
|
||||||
|
|
||||||
@ -164,16 +196,11 @@ class PostCreator
|
|||||||
{ user: @user,
|
{ user: @user,
|
||||||
limit_once_per: 24.hours,
|
limit_once_per: 24.hours,
|
||||||
message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
|
message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
|
||||||
elsif @post && !@post.errors.present? && !skip_validations?
|
elsif @post && errors.blank? && !skip_validations?
|
||||||
SpamRulesEnforcer.enforce!(@post)
|
SpamRulesEnforcer.enforce!(@post)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def plugin_callbacks
|
|
||||||
DiscourseEvent.trigger :before_create_post, @post
|
|
||||||
DiscourseEvent.trigger :validate_post, @post
|
|
||||||
end
|
|
||||||
|
|
||||||
def track_latest_on_category
|
def track_latest_on_category
|
||||||
return unless @post && @post.errors.count == 0 && @topic && @topic.category_id
|
return unless @post && @post.errors.count == 0 && @topic && @topic.category_id
|
||||||
|
|
||||||
@ -191,27 +218,17 @@ class PostCreator
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def setup_topic
|
def create_topic
|
||||||
if new_topic?
|
return if @topic
|
||||||
|
begin
|
||||||
topic_creator = TopicCreator.new(@user, guardian, @opts)
|
topic_creator = TopicCreator.new(@user, guardian, @opts)
|
||||||
|
@topic = topic_creator.create
|
||||||
begin
|
rescue ActiveRecord::Rollback
|
||||||
topic = topic_creator.create
|
add_errors_from(topic_creator)
|
||||||
@errors = topic_creator.errors
|
return
|
||||||
rescue ActiveRecord::Rollback => ex
|
|
||||||
# In the event of a rollback, grab the errors from the topic
|
|
||||||
@errors = topic_creator.errors
|
|
||||||
raise ex
|
|
||||||
end
|
|
||||||
else
|
|
||||||
topic = Topic.find_by(id: @opts[:topic_id])
|
|
||||||
if (topic.blank? || !guardian.can_create?(Post, topic))
|
|
||||||
@errors = Post.new.errors
|
|
||||||
@errors.add(:base, I18n.t(:topic_not_found))
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@topic = topic
|
@post.topic_id = @topic.id
|
||||||
|
@post.topic = @topic
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_topic_stats
|
def update_topic_stats
|
||||||
@ -232,9 +249,10 @@ class PostCreator
|
|||||||
def setup_post
|
def setup_post
|
||||||
@opts[:raw] = TextCleaner.normalize_whitespaces(@opts[:raw]).gsub(/\s+\z/, "")
|
@opts[:raw] = TextCleaner.normalize_whitespaces(@opts[:raw]).gsub(/\s+\z/, "")
|
||||||
|
|
||||||
post = @topic.posts.new(raw: @opts[:raw],
|
post = Post.new(raw: @opts[:raw],
|
||||||
user: @user,
|
topic_id: @topic.try(:id),
|
||||||
reply_to_post_number: @opts[:reply_to_post_number])
|
user: @user,
|
||||||
|
reply_to_post_number: @opts[:reply_to_post_number])
|
||||||
|
|
||||||
# Attributes we pass through to the post instance if present
|
# Attributes we pass through to the post instance if present
|
||||||
[:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes, :cook_method, :via_email, :raw_email].each do |a|
|
[:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes, :cook_method, :via_email, :raw_email].each do |a|
|
||||||
@ -251,21 +269,9 @@ class PostCreator
|
|||||||
@post = post
|
@post = post
|
||||||
end
|
end
|
||||||
|
|
||||||
def rollback_if_host_spam_detected
|
|
||||||
return if skip_validations?
|
|
||||||
if @post.has_host_spam?
|
|
||||||
@post.errors.add(:base, I18n.t(:spamming_host))
|
|
||||||
@errors = @post.errors
|
|
||||||
@spam = true
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def save_post
|
def save_post
|
||||||
unless @post.save(validate: !skip_validations?)
|
saved = @post.save(validate: !skip_validations?)
|
||||||
@errors = @post.errors
|
rollback_from_errors!(@post) unless saved
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_unique_post_key
|
def store_unique_post_key
|
||||||
|
41
lib/post_enqueuer.rb
Normal file
41
lib/post_enqueuer.rb
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
require_dependency 'topic_creator'
|
||||||
|
require_dependency 'queued_post'
|
||||||
|
require_dependency 'has_errors'
|
||||||
|
|
||||||
|
class PostEnqueuer
|
||||||
|
include HasErrors
|
||||||
|
|
||||||
|
def initialize(user, queue)
|
||||||
|
@user = user
|
||||||
|
@queue = queue
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue(args)
|
||||||
|
|
||||||
|
queued_post = QueuedPost.new(queue: @queue,
|
||||||
|
state: QueuedPost.states[:new],
|
||||||
|
user_id: @user.id,
|
||||||
|
topic_id: args[:topic_id],
|
||||||
|
raw: args[:raw],
|
||||||
|
post_options: args[:post_options] || {})
|
||||||
|
|
||||||
|
validate_method = :"validate_#{@queue}"
|
||||||
|
if respond_to?(validate_method)
|
||||||
|
return unless send(validate_method, queued_post.create_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
add_errors_from(queued_post) unless queued_post.save
|
||||||
|
queued_post
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_new_topic(create_options)
|
||||||
|
topic_creator = TopicCreator.new(@user, Guardian.new(@user), create_options)
|
||||||
|
validate_child(topic_creator)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_new_post(create_options)
|
||||||
|
post_creator = PostCreator.new(@user, create_options)
|
||||||
|
validate_child(post_creator)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -1,6 +1,7 @@
|
|||||||
class TopicCreator
|
require_dependency 'has_errors'
|
||||||
|
|
||||||
attr_accessor :errors
|
class TopicCreator
|
||||||
|
include HasErrors
|
||||||
|
|
||||||
def self.create(user, guardian, opts)
|
def self.create(user, guardian, opts)
|
||||||
self.new(user, guardian, opts).create
|
self.new(user, guardian, opts).create
|
||||||
@ -13,55 +14,52 @@ class TopicCreator
|
|||||||
@added_users = []
|
@added_users = []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
topic = Topic.new(setup_topic_params)
|
||||||
|
validate_child(topic)
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@topic = Topic.new(setup_topic_params)
|
topic = Topic.new(setup_topic_params)
|
||||||
|
|
||||||
setup_auto_close_time
|
setup_auto_close_time(topic)
|
||||||
process_private_message
|
process_private_message(topic)
|
||||||
save_topic
|
save_topic(topic)
|
||||||
create_warning
|
create_warning(topic)
|
||||||
watch_topic
|
watch_topic(topic)
|
||||||
|
|
||||||
@topic
|
topic
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_warning
|
def create_warning(topic)
|
||||||
return unless @opts[:is_warning]
|
return unless @opts[:is_warning]
|
||||||
|
|
||||||
# We can only attach warnings to PMs
|
# We can only attach warnings to PMs
|
||||||
unless @topic.private_message?
|
rollback_with!(topic, :warning_requires_pm) unless topic.private_message?
|
||||||
@topic.errors.add(:base, :warning_requires_pm)
|
|
||||||
@errors = @topic.errors
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# Don't create it if there is more than one user
|
# Don't create it if there is more than one user
|
||||||
if @added_users.size != 1
|
rollback_with!(topic, :too_many_users) if @added_users.size != 1
|
||||||
@topic.errors.add(:base, :too_many_users)
|
|
||||||
@errors = @topic.errors
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create a warning record
|
# Create a warning record
|
||||||
Warning.create(topic: @topic, user: @added_users.first, created_by: @user)
|
Warning.create(topic: topic, user: @added_users.first, created_by: @user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def watch_topic
|
def watch_topic(topic)
|
||||||
unless @opts[:auto_track] == false
|
unless @opts[:auto_track] == false
|
||||||
@topic.notifier.watch_topic!(@topic.user_id)
|
topic.notifier.watch_topic!(topic.user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
user_ids = @topic.topic_allowed_users(true).pluck(:user_id)
|
user_ids = topic.topic_allowed_users(true).pluck(:user_id)
|
||||||
user_ids += @topic.topic_allowed_groups(true).map { |t| t.group.users.pluck(:id) }.flatten
|
user_ids += topic.topic_allowed_groups(true).map { |t| t.group.users.pluck(:id) }.flatten
|
||||||
|
|
||||||
user_ids.uniq.reject{ |id| id == @topic.user_id }.each do |user_id|
|
user_ids.uniq.reject{ |id| id == topic.user_id }.each do |user_id|
|
||||||
@topic.notifier.watch_topic!(user_id, nil) unless user_id == -1
|
topic.notifier.watch_topic!(user_id, nil) unless user_id == -1
|
||||||
end
|
end
|
||||||
|
|
||||||
CategoryUser.auto_watch_new_topic(@topic)
|
CategoryUser.auto_watch_new_topic(topic)
|
||||||
CategoryUser.auto_track_new_topic(@topic)
|
CategoryUser.auto_track_new_topic(topic)
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_topic_params
|
def setup_topic_params
|
||||||
@ -106,31 +104,28 @@ class TopicCreator
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_auto_close_time
|
def setup_auto_close_time(topic)
|
||||||
return unless @opts[:auto_close_time].present?
|
return unless @opts[:auto_close_time].present?
|
||||||
return unless @guardian.can_moderate?(@topic)
|
return unless @guardian.can_moderate?(topic)
|
||||||
@topic.set_auto_close(@opts[:auto_close_time], @user)
|
topic.set_auto_close(@opts[:auto_close_time], @user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_private_message
|
def process_private_message(topic)
|
||||||
return unless @opts[:archetype] == Archetype.private_message
|
return unless @opts[:archetype] == Archetype.private_message
|
||||||
@topic.subtype = TopicSubtype.user_to_user unless @topic.subtype
|
topic.subtype = TopicSubtype.user_to_user unless topic.subtype
|
||||||
|
|
||||||
unless @opts[:target_usernames].present? || @opts[:target_group_names].present?
|
unless @opts[:target_usernames].present? || @opts[:target_group_names].present?
|
||||||
@topic.errors.add(:base, :no_user_selected)
|
rollback_with!(topic, :no_user_selected)
|
||||||
@errors = @topic.errors
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
add_users(@topic,@opts[:target_usernames])
|
add_users(topic,@opts[:target_usernames])
|
||||||
add_groups(@topic,@opts[:target_group_names])
|
add_groups(topic,@opts[:target_group_names])
|
||||||
@topic.topic_allowed_users.build(user_id: @user.id)
|
topic.topic_allowed_users.build(user_id: @user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_topic
|
def save_topic(topic)
|
||||||
unless @topic.save(validate: !@opts[:skip_validations])
|
unless topic.save(validate: !@opts[:skip_validations])
|
||||||
@errors = @topic.errors
|
rollback_from_errors!(topic)
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -146,16 +141,12 @@ class TopicCreator
|
|||||||
def add_groups(topic, groups)
|
def add_groups(topic, groups)
|
||||||
return unless groups
|
return unless groups
|
||||||
Group.where(name: groups.split(',')).each do |group|
|
Group.where(name: groups.split(',')).each do |group|
|
||||||
check_can_send_permission!(topic,group)
|
check_can_send_permission!(topic, group)
|
||||||
topic.topic_allowed_groups.build(group_id: group.id)
|
topic.topic_allowed_groups.build(group_id: group.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_can_send_permission!(topic,item)
|
def check_can_send_permission!(topic, obj)
|
||||||
unless @guardian.can_send_private_message?(item)
|
rollback_with!(topic, :cant_send_pm) unless @guardian.can_send_private_message?(obj)
|
||||||
topic.errors.add(:base, :cant_send_pm)
|
|
||||||
@errors = topic.errors
|
|
||||||
raise ActiveRecord::Rollback.new
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
require_dependency 'validators/stripped_length_validator'
|
require_dependency 'validators/stripped_length_validator'
|
||||||
module Validators; end
|
module Validators; end
|
||||||
class Validators::PostValidator < ActiveModel::Validator
|
class Validators::PostValidator < ActiveModel::Validator
|
||||||
|
|
||||||
def validate(record)
|
def validate(record)
|
||||||
presence(record)
|
presence(record)
|
||||||
unless Discourse.static_doc_topic_ids.include?(record.topic_id) && record.acting_user.try(:admin?)
|
unless Discourse.static_doc_topic_ids.include?(record.topic_id) && record.acting_user.try(:admin?)
|
||||||
@ -16,9 +17,12 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||||||
end
|
end
|
||||||
|
|
||||||
def presence(post)
|
def presence(post)
|
||||||
[:raw,:topic_id].each do |attr_name|
|
|
||||||
post.errors.add(attr_name, :blank, options) if post.send(attr_name).blank?
|
post.errors.add(:raw, :blank, options) if post.raw.blank?
|
||||||
|
unless options[:skip_topic]
|
||||||
|
post.errors.add(:topic_id, :blank, options) if post.topic_id.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
if post.new_record? and post.user_id.nil?
|
if post.new_record? and post.user_id.nil?
|
||||||
post.errors.add(:user_id, :blank, options)
|
post.errors.add(:user_id, :blank, options)
|
||||||
end
|
end
|
||||||
|
55
spec/components/has_errors_spec.rb
Normal file
55
spec/components/has_errors_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'has_errors'
|
||||||
|
|
||||||
|
describe HasErrors do
|
||||||
|
|
||||||
|
class ErrorTestClass
|
||||||
|
include HasErrors
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:error_test) { ErrorTestClass.new }
|
||||||
|
let(:title_error) { "Title can't be blank" }
|
||||||
|
|
||||||
|
# No title is invalid
|
||||||
|
let(:invalid_topic) { Fabricate.build(:topic, title: '') }
|
||||||
|
|
||||||
|
it "has no errors by default" do
|
||||||
|
expect(error_test.errors).to be_blank
|
||||||
|
end
|
||||||
|
|
||||||
|
context "validate_child" do
|
||||||
|
it "adds the errors from invalid AR objects" do
|
||||||
|
expect(error_test.validate_child(invalid_topic)).to eq(false)
|
||||||
|
expect(error_test.errors).to be_present
|
||||||
|
expect(error_test.errors[:base]).to include(title_error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't add the errors from valid AR objects" do
|
||||||
|
topic = Fabricate.build(:topic)
|
||||||
|
expect(error_test.validate_child(topic)).to eq(true)
|
||||||
|
expect(error_test.errors).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "rollback_from_errors!" do
|
||||||
|
it "triggers a rollback" do
|
||||||
|
invalid_topic.valid?
|
||||||
|
|
||||||
|
expect(-> { error_test.rollback_from_errors!(invalid_topic) }).to raise_error(ActiveRecord::Rollback)
|
||||||
|
expect(error_test.errors).to be_present
|
||||||
|
expect(error_test.errors[:base]).to include(title_error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "rollback_with_error!" do
|
||||||
|
it "triggers a rollback" do
|
||||||
|
|
||||||
|
expect(-> {
|
||||||
|
error_test.rollback_with!(invalid_topic, :custom_error)
|
||||||
|
}).to raise_error(ActiveRecord::Rollback)
|
||||||
|
expect(error_test.errors).to be_present
|
||||||
|
expect(error_test.errors[:base]).to include(:custom_error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -311,7 +311,8 @@ describe PostCreator do
|
|||||||
|
|
||||||
it "does not create the post" do
|
it "does not create the post" do
|
||||||
GroupMessage.stubs(:create)
|
GroupMessage.stubs(:create)
|
||||||
creator.create
|
post = creator.create
|
||||||
|
|
||||||
expect(creator.errors).to be_present
|
expect(creator.errors).to be_present
|
||||||
expect(creator.spam?).to eq(true)
|
expect(creator.spam?).to eq(true)
|
||||||
end
|
end
|
||||||
@ -521,7 +522,7 @@ describe PostCreator do
|
|||||||
it 'can save a post' do
|
it 'can save a post' do
|
||||||
creator = PostCreator.new(user, raw: 'q', title: 'q', skip_validations: true)
|
creator = PostCreator.new(user, raw: 'q', title: 'q', skip_validations: true)
|
||||||
creator.create
|
creator.create
|
||||||
expect(creator.errors).to eq(nil)
|
expect(creator.errors).to be_blank
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -573,7 +574,8 @@ describe PostCreator do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't strip starting whitespaces" do
|
it "doesn't strip starting whitespaces" do
|
||||||
post = PostCreator.new(user, { title: "testing whitespace stripping", raw: " <-- whitespaces --> " }).create
|
pc = PostCreator.new(user, { title: "testing whitespace stripping", raw: " <-- whitespaces --> " })
|
||||||
|
post = pc.create
|
||||||
expect(post.raw).to eq(" <-- whitespaces -->")
|
expect(post.raw).to eq(" <-- whitespaces -->")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
47
spec/components/post_enqueuer_spec.rb
Normal file
47
spec/components/post_enqueuer_spec.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require_dependency 'post_enqueuer'
|
||||||
|
|
||||||
|
describe PostEnqueuer do
|
||||||
|
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
context 'with valid arguments' do
|
||||||
|
let(:topic) { Fabricate(:topic) }
|
||||||
|
let(:enqueuer) { PostEnqueuer.new(user, 'new_post') }
|
||||||
|
|
||||||
|
it 'enqueues the post' do
|
||||||
|
qp = enqueuer.enqueue(raw: 'This should be enqueued',
|
||||||
|
topic_id: topic.id,
|
||||||
|
post_options: { reply_to_post_number: 1 })
|
||||||
|
|
||||||
|
expect(enqueuer.errors).to be_blank
|
||||||
|
expect(qp).to be_present
|
||||||
|
expect(qp.topic).to eq(topic)
|
||||||
|
expect(qp.user).to eq(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "topic validations" do
|
||||||
|
let(:enqueuer) { PostEnqueuer.new(user, 'new_topic') }
|
||||||
|
|
||||||
|
it "doesn't enqueue the post" do
|
||||||
|
qp = enqueuer.enqueue(raw: 'This should not be enqueued',
|
||||||
|
post_options: { title: 'too short' })
|
||||||
|
|
||||||
|
expect(enqueuer.errors).to be_present
|
||||||
|
expect(qp).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "post validations" do
|
||||||
|
let(:enqueuer) { PostEnqueuer.new(user, 'new_post') }
|
||||||
|
|
||||||
|
it "doesn't enqueue the post" do
|
||||||
|
qp = enqueuer.enqueue(raw: 'too short')
|
||||||
|
|
||||||
|
expect(enqueuer.errors).to be_present
|
||||||
|
expect(qp).to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -19,7 +19,7 @@ describe QueuedPost do
|
|||||||
auto_track: true,
|
auto_track: true,
|
||||||
custom_fields: { hello: 'world' },
|
custom_fields: { hello: 'world' },
|
||||||
cooking_options: { cat: 'hat' },
|
cooking_options: { cat: 'hat' },
|
||||||
cook_method: 'regular',
|
cook_method: Post.cook_methods[:raw_html],
|
||||||
not_create_option: true,
|
not_create_option: true,
|
||||||
image_sizes: {"http://foo.bar/image.png" => {"width" => 0, "height" => 222}}
|
image_sizes: {"http://foo.bar/image.png" => {"width" => 0, "height" => 222}}
|
||||||
}) }
|
}) }
|
||||||
@ -34,8 +34,8 @@ describe QueuedPost do
|
|||||||
expect(create_options[:raw_email]).to eq('store_me')
|
expect(create_options[:raw_email]).to eq('store_me')
|
||||||
expect(create_options[:auto_track]).to eq(true)
|
expect(create_options[:auto_track]).to eq(true)
|
||||||
expect(create_options[:custom_fields]).to eq('hello' => 'world')
|
expect(create_options[:custom_fields]).to eq('hello' => 'world')
|
||||||
expect(create_options[:cooking_options]).to eq('cat' => 'hat')
|
expect(create_options[:cooking_options]).to eq(cat: 'hat')
|
||||||
expect(create_options[:cook_method]).to eq('regular')
|
expect(create_options[:cook_method]).to eq(Post.cook_methods[:raw_html])
|
||||||
expect(create_options[:not_create_option]).to eq(nil)
|
expect(create_options[:not_create_option]).to eq(nil)
|
||||||
expect(create_options[:image_sizes]).to eq("http://foo.bar/image.png" => {"width" => 0, "height" => 222})
|
expect(create_options[:image_sizes]).to eq("http://foo.bar/image.png" => {"width" => 0, "height" => 222})
|
||||||
end
|
end
|
||||||
@ -47,6 +47,7 @@ describe QueuedPost do
|
|||||||
expect(post).to be_present
|
expect(post).to be_present
|
||||||
expect(post).to be_valid
|
expect(post).to be_valid
|
||||||
expect(post.topic).to eq(topic)
|
expect(post.topic).to eq(topic)
|
||||||
|
expect(post.custom_fields['hello']).to eq('world')
|
||||||
|
|
||||||
# Updates the QP record
|
# Updates the QP record
|
||||||
expect(qp.approved_by).to eq(admin)
|
expect(qp.approved_by).to eq(admin)
|
||||||
@ -74,50 +75,52 @@ describe QueuedPost do
|
|||||||
context "creating a topic" do
|
context "creating a topic" do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:admin) { Fabricate(:admin) }
|
let(:admin) { Fabricate(:admin) }
|
||||||
let!(:category) { Fabricate(:category) }
|
|
||||||
|
|
||||||
let(:qp) { QueuedPost.create(queue: 'new_topic',
|
context "with a valid topic" do
|
||||||
state: QueuedPost.states[:new],
|
let!(:category) { Fabricate(:category) }
|
||||||
user_id: user.id,
|
let(:qp) { QueuedPost.create(queue: 'new_topic',
|
||||||
raw: 'This post should be queued up',
|
state: QueuedPost.states[:new],
|
||||||
post_options: {
|
user_id: user.id,
|
||||||
title: 'This is the topic title to queue up',
|
raw: 'This post should be queued up',
|
||||||
archetype: 'regular',
|
post_options: {
|
||||||
category: category.id,
|
title: 'This is the topic title to queue up',
|
||||||
meta_data: {evil: 'trout'}
|
archetype: 'regular',
|
||||||
}) }
|
category: category.id,
|
||||||
|
meta_data: {evil: 'trout'}
|
||||||
|
}) }
|
||||||
|
|
||||||
|
|
||||||
it "returns the appropriate options for creating a topic" do
|
it "returns the appropriate options for creating a topic" do
|
||||||
create_options = qp.create_options
|
create_options = qp.create_options
|
||||||
|
|
||||||
expect(create_options[:category]).to eq(category.id)
|
expect(create_options[:category]).to eq(category.id)
|
||||||
expect(create_options[:archetype]).to eq('regular')
|
expect(create_options[:archetype]).to eq('regular')
|
||||||
expect(create_options[:meta_data]).to eq('evil' => 'trout')
|
expect(create_options[:meta_data]).to eq('evil' => 'trout')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates the post and topic" do
|
it "creates the post and topic" do
|
||||||
topic_count, post_count = Topic.count, Post.count
|
topic_count, post_count = Topic.count, Post.count
|
||||||
post = qp.approve!(admin)
|
post = qp.approve!(admin)
|
||||||
|
|
||||||
expect(Topic.count).to eq(topic_count + 1)
|
expect(Topic.count).to eq(topic_count + 1)
|
||||||
expect(Post.count).to eq(post_count + 1)
|
expect(Post.count).to eq(post_count + 1)
|
||||||
|
|
||||||
expect(post).to be_present
|
expect(post).to be_present
|
||||||
expect(post).to be_valid
|
expect(post).to be_valid
|
||||||
|
|
||||||
topic = post.topic
|
topic = post.topic
|
||||||
expect(topic).to be_present
|
expect(topic).to be_present
|
||||||
expect(topic.category).to eq(category)
|
expect(topic.category).to eq(category)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't create the post and topic" do
|
it "doesn't create the post and topic" do
|
||||||
topic_count, post_count = Topic.count, Post.count
|
topic_count, post_count = Topic.count, Post.count
|
||||||
|
|
||||||
qp.reject!(admin)
|
qp.reject!(admin)
|
||||||
|
|
||||||
expect(Topic.count).to eq(topic_count)
|
expect(Topic.count).to eq(topic_count)
|
||||||
expect(Post.count).to eq(post_count)
|
expect(Post.count).to eq(post_count)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user