discourse/app/controllers/posts_controller.rb
Sam 4a20d09523 distributed memoizer added to ensure absolute duplicate posts don't get through
in case of an absolute dupe just return the memoized post

This works around issues with wordpress being crazy
2013-07-29 12:25:19 +10:00

250 lines
6.9 KiB
Ruby

require_dependency 'post_creator'
require_dependency 'post_destroyer'
require_dependency 'distributed_memoizer'
class PostsController < ApplicationController
# Need to be logged in for all actions here
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :versions]
skip_before_filter :store_incoming_links, only: [:short_link]
skip_before_filter :check_xhr, only: [:markdown,:short_link]
def markdown
post = Post.where(topic_id: params[:topic_id].to_i, post_number: (params[:post_number] || 1).to_i).first
if post && guardian.can_see?(post)
render text: post.raw, content_type: 'text/plain'
else
raise Discourse::NotFound
end
end
def short_link
post = Post.find(params[:post_id].to_i)
IncomingLink.add(request,current_user)
redirect_to post.url
end
def create
params = create_params
key = params_key(params)
result = DistributedMemoizer.memoize(key, 120) do
post_creator = PostCreator.new(current_user, params)
post = post_creator.create
if post_creator.errors.present?
# If the post was spam, flag all the user's posts as spam
current_user.flag_linked_posts_as_spam if post_creator.spam?
"e" << MultiJson.dump(errors: post_creator.errors.full_messages)
else
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
post_serializer.topic_slug = post.topic.slug if post.topic.present?
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
"s" << MultiJson.dump(post_serializer)
end
end
payload = result[1..-1]
if result[0] == "e"
# don't memoize errors
$redis.del(DistributedMemoizer.redis_key(key))
render json: payload, status: 422
else
render json: payload
end
end
def update
params.require(:post)
post = Post.where(id: params[:id]).first
post.image_sizes = params[:image_sizes] if params[:image_sizes].present?
guardian.ensure_can_edit!(post)
# to stay consistent with the create api,
# we should allow for title changes and category changes here
# we should also move all of this to a post updater.
if post.post_number == 1 && (params[:title] || params[:post][:category])
post.topic.title = params[:title] if params[:title]
Topic.transaction do
post.topic.change_category(params[:post][:category])
post.topic.save
end
if post.topic.errors.present?
render_json_error(post.topic)
return
end
end
revisor = PostRevisor.new(post)
if revisor.revise!(current_user, params[:post][:raw])
TopicLink.extract_from(post)
end
if post.errors.present?
render_json_error(post)
return
end
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
link_counts = TopicLink.counts_for(guardian,post.topic, [post])
post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
post_serializer.topic_slug = post.topic.slug if post.topic.present?
result = {post: post_serializer.as_json}
if revisor.category_changed.present?
result[:category] = BasicCategorySerializer.new(revisor.category_changed, scope: guardian, root: false).as_json
end
render_json_dump(result)
end
def by_number
@post = Post.where(topic_id: params[:topic_id], post_number: params[:post_number]).first
guardian.ensure_can_see!(@post)
@post.revert_to(params[:version].to_i) if params[:version].present?
render_post_json(@post)
end
def show
@post = find_post_from_params
@post.revert_to(params[:version].to_i) if params[:version].present?
render_post_json(@post)
end
def destroy
post = find_post_from_params
guardian.ensure_can_delete!(post)
destroyer = PostDestroyer.new(current_user, post)
destroyer.destroy
render nothing: true
end
def recover
post = find_post_from_params
guardian.ensure_can_recover_post!(post)
destroyer = PostDestroyer.new(current_user, post)
destroyer.recover
post.reload
render_post_json(post)
end
def destroy_many
params.require(:post_ids)
posts = Post.where(id: params[:post_ids])
raise Discourse::InvalidParameters.new(:post_ids) if posts.blank?
# Make sure we can delete the posts
posts.each {|p| guardian.ensure_can_delete!(p) }
Post.transaction do
topic_id = posts.first.topic_id
posts.each {|p| p.destroy }
Topic.reset_highest(topic_id)
end
render nothing: true
end
# Retrieves a list of versions and who made them for a post
def versions
post = find_post_from_params
render_serialized(post.all_versions, VersionSerializer)
end
# Direct replies to this post
def replies
post = find_post_from_params
render_serialized(post.replies, PostSerializer)
end
# Returns the "you're creating a post education"
def education_text
end
def bookmark
post = find_post_from_params
if current_user
if params[:bookmarked] == "true"
PostAction.act(current_user, post, PostActionType.types[:bookmark])
else
PostAction.remove_act(current_user, post, PostActionType.types[:bookmark])
end
end
render nothing: true
end
protected
def find_post_from_params
finder = Post.where(id: params[:id] || params[:post_id])
# Include deleted posts if the user is staff
finder = finder.with_deleted if current_user.try(:staff?)
post = finder.first
guardian.ensure_can_see!(post)
post
end
def render_post_json(post)
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
post_serializer.add_raw = true
render_json_dump(post_serializer)
end
private
def params_key(params)
"post##" << Digest::SHA1.hexdigest(params
.to_a
.concat([["user", current_user.id]])
.sort{|x,y| x[0] <=> y[0]}.join do |x,y|
"#{x}:#{y}"
end)
end
def create_params
permitted = [
:raw,
:topic_id,
:title,
:archetype,
:category,
:target_usernames,
:reply_to_post_number,
:image_sizes,
:auto_close_days,
:auto_track
]
# param munging for WordPress
params[:auto_track] = !(params[:auto_track].to_s == "false") if params[:auto_track]
if api_key_valid?
# php seems to be sending this incorrectly, don't fight with it
params[:skip_validations] = params[:skip_validations].to_s == "true"
permitted << :skip_validations
end
params.require(:raw)
params.permit(*permitted).tap do |whitelisted|
# TODO this does not feel right, we should name what meta_data is allowed
whitelisted[:meta_data] = params[:meta_data]
end
end
end