From 1d24d8471ef3cad7df341468a1e5ba14c1761460 Mon Sep 17 00:00:00 2001
From: riking <rikingcoding@gmail.com>
Date: Fri, 23 Jan 2015 21:04:14 -0800
Subject: [PATCH] FEATURE: Latest posts endpoint at /posts.json

---
 app/controllers/posts_controller.rb | 23 +++++++++++++++++++-
 app/serializers/post_serializer.rb  | 33 +++++++++++++++++++++++------
 config/routes.rb                    |  1 +
 3 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 2d0b936d0..26c190087 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -5,7 +5,7 @@ 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, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown, :raw, :cooked]
+  before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest]
 
   skip_before_filter :check_xhr, only: [:markdown_id, :markdown_num, :short_link]
 
@@ -25,6 +25,27 @@ class PostsController < ApplicationController
     end
   end
 
+  def latest
+    posts = Post.order(created_at: :desc)
+                .where('posts.id > ?', Post.last.id - 50) # last 50 post IDs only, to avoid counting deleted posts in security check
+                .includes(topic: :category)
+                .includes(:user)
+                .limit(50)
+    # Remove posts the user doesn't have permission to see
+    # This isn't leaking any information we weren't already through the post ID numbers
+    posts = posts.reject { |post| !guardian.can_see?(post) }
+
+    counts = PostAction.counts_for(posts, current_user)
+
+    render_json_dump(serialize_data(posts,
+                                    PostSerializer,
+                                    scope: guardian,
+                                    root: 'latest_posts',
+                                    add_raw: true,
+                                    all_post_actions: counts)
+    )
+  end
+
   def cooked
     post = find_post_from_params
     render json: {cooked: post.cooked}
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index b6ca0ba92..bb99bfd85 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -1,12 +1,17 @@
 class PostSerializer < BasicPostSerializer
 
   # To pass in additional information we might need
-  attr_accessor :topic_view,
+  INSTANCE_VARS = [:topic_view,
                 :parent_post,
                 :add_raw,
                 :single_post_link_counts,
                 :draft_sequence,
-                :post_actions
+                :post_actions,
+                :all_post_actions]
+
+  INSTANCE_VARS.each do |v|
+    self.send(:attr_accessor, v)
+  end
 
   attributes :post_number,
              :post_type,
@@ -53,6 +58,15 @@ class PostSerializer < BasicPostSerializer
              :static_doc,
              :via_email
 
+  def initialize(object, opts)
+    super(object, opts)
+    PostSerializer::INSTANCE_VARS.each do |name|
+      if opts.include? name
+        self.send("#{name}=", opts[name])
+      end
+    end
+  end
+
   def topic_slug
     object.try(:topic).try(:slug)
   end
@@ -154,6 +168,13 @@ class PostSerializer < BasicPostSerializer
     scope.is_staff? && object.deleted_by.present?
   end
 
+  # Helper function to decide between #post_actions and @all_post_actions
+  def actions
+    return post_actions if post_actions.present?
+    return all_post_actions[object.id] if all_post_actions.present?
+    nil
+  end
+
   # Summary of the actions taken on this post
   def actions_summary
     result = []
@@ -167,7 +188,7 @@ class PostSerializer < BasicPostSerializer
         id: id,
         count: count,
         hidden: (sym == :vote),
-        can_act: scope.post_can_act?(object, sym, taken_actions: post_actions)
+        can_act: scope.post_can_act?(object, sym, taken_actions: actions)
       }
 
       if sym == :notify_user && scope.current_user.present? && scope.current_user == object.user
@@ -182,9 +203,9 @@ class PostSerializer < BasicPostSerializer
                                            active_flags[id].count > 0
       end
 
-      if post_actions.present? && post_actions.has_key?(id)
+      if actions.present? && actions.has_key?(id)
         action_summary[:acted] = true
-        action_summary[:can_undo] = scope.can_delete?(post_actions[id])
+        action_summary[:can_undo] = scope.can_delete?(actions[id])
       end
 
       # only show public data
@@ -225,7 +246,7 @@ class PostSerializer < BasicPostSerializer
   end
 
   def include_bookmarked?
-    post_actions.present? && post_actions.keys.include?(PostActionType.types[:bookmark])
+    actions.present? && actions.keys.include?(PostActionType.types[:bookmark])
   end
 
   def include_display_username?
diff --git a/config/routes.rb b/config/routes.rb
index 3c288b57d..8a41b8be1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -269,6 +269,7 @@ Discourse::Application.routes.draw do
   get "uploads/:site/:sha" => "uploads#show", constraints: { site: /\w+/, sha: /[a-z0-9]{40}/}
   post "uploads" => "uploads#create"
 
+  get "posts" => "posts#latest"
   get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
   get "posts/:id/reply-history" => "posts#reply_history"
   get "posts/:username/deleted" => "posts#deleted_posts", constraints: {username: USERNAME_ROUTE_FORMAT}