From 34469e725b84a9f58aed8cde3c7d3f61fe9f4597 Mon Sep 17 00:00:00 2001
From: Arpit Jalan <arpit@techapj.com>
Date: Mon, 21 Mar 2016 15:52:36 +0530
Subject: [PATCH] FEATURE: separate API endpoints for public and private posts

---
 app/controllers/posts_controller.rb       | 35 +++++++++----
 config/locales/server.en.yml              |  1 +
 config/routes.rb                          |  3 +-
 spec/controllers/posts_controller_spec.rb | 61 +++++++++++++++++------
 4 files changed, 73 insertions(+), 27 deletions(-)

diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index fdf3dbb68..f97fc1aad 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -37,14 +37,29 @@ class PostsController < ApplicationController
     last_post_id = params[:before].to_i
     last_post_id = Post.last.id if last_post_id <= 0
 
-    # last 50 post IDs only, to avoid counting deleted posts in security check
-    posts = Post.order(created_at: :desc)
-                .where('posts.id <= ?', last_post_id)
-                .where('posts.id > ?', last_post_id - 50)
-                .includes(topic: :category)
-                .includes(user: :primary_group)
-                .includes(:reply_to_user)
-                .limit(50)
+    if params[:id] == "private_posts"
+      raise Discourse::NotFound if current_user.nil?
+      posts = Post.private_posts
+                  .order(created_at: :desc)
+                  .where('posts.id <= ?', last_post_id)
+                  .where('posts.id > ?', last_post_id - 50)
+                  .includes(topic: :category)
+                  .includes(user: :primary_group)
+                  .includes(:reply_to_user)
+                  .limit(50)
+      rss_description = I18n.t("rss_description.private_posts")
+    else
+      posts = Post.public_posts
+                  .order(created_at: :desc)
+                  .where('posts.id <= ?', last_post_id)
+                  .where('posts.id > ?', last_post_id - 50)
+                  .includes(topic: :category)
+                  .includes(user: :primary_group)
+                  .includes(:reply_to_user)
+                  .limit(50)
+      rss_description = I18n.t("rss_description.posts")
+    end
+
     # 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) || post.topic.blank? }
@@ -53,7 +68,7 @@ class PostsController < ApplicationController
     respond_to do |format|
       format.rss do
         @posts = posts
-        @title = "#{SiteSetting.title} - #{I18n.t("rss_description.posts")}"
+        @title = "#{SiteSetting.title} - #{rss_description}"
         @link = Discourse.base_url
         @description = I18n.t("rss_description.posts")
         render 'posts/latest', formats: [:rss]
@@ -62,7 +77,7 @@ class PostsController < ApplicationController
         render_json_dump(serialize_data(posts,
                                         PostSerializer,
                                         scope: guardian,
-                                        root: 'latest_posts',
+                                        root: params[:id],
                                         add_raw: true,
                                         add_title: true,
                                         all_post_actions: counts)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index adf0b0dcd..020235985 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -204,6 +204,7 @@ en:
     hot: "Hot topics"
     top: "Top topics"
     posts: "Latest posts"
+    private_posts: "Latest private messages"
     group_posts: "Latest posts from %{group_name}"
     group_mentions: "Latest mentions from %{group_name}"
   too_late_to_edit: "That post was created too long ago. It can no longer be edited or deleted."
diff --git a/config/routes.rb b/config/routes.rb
index fab10862b..80a1533d4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -347,7 +347,8 @@ Discourse::Application.routes.draw do
   # used to download attachments (old route)
   get "uploads/:site/:id/:sha" => "uploads#show", constraints: { site: /\w+/, id: /\d+/, sha: /[a-f0-9]{16}/ }
 
-  get "posts" => "posts#latest"
+  get "posts" => "posts#latest", id: "latest_posts"
+  get "private-posts" => "posts#latest", id: "private_posts"
   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}
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index 67392c081..635798e8d 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -55,27 +55,56 @@ describe PostsController do
 
   describe 'latest' do
     let(:user) { log_in }
-    let!(:post) { Fabricate(:post, user: user) }
+    let!(:public_topic) { Fabricate(:topic) }
+    let!(:post) { Fabricate(:post, user: user, topic: public_topic) }
+    let!(:private_topic) { Fabricate(:topic, archetype: Archetype.private_message, category: nil) }
+    let!(:private_post) { Fabricate(:post, user: user, topic: private_topic) }
     let!(:topicless_post) { Fabricate(:post, user: user, raw: '<p>Car 54, where are you?</p>') }
 
-    before do
-      topicless_post.update topic_id: -100
+    context "public posts" do
+      before do
+        topicless_post.update topic_id: -100
+      end
+
+      it 'returns public posts with topic for json' do
+        xhr :get, :latest, id: "latest_posts", format: :json
+        expect(response).to be_success
+        json = ::JSON.parse(response.body)
+        post_ids = json['latest_posts'].map { |p| p['id'] }
+        expect(post_ids).to include post.id
+        expect(post_ids).to_not include private_post.id
+        expect(post_ids).to_not include topicless_post.id
+      end
+
+      it 'returns public posts with topic for rss' do
+        xhr :get, :latest, id: "latest_posts", format: :rss
+        expect(response).to be_success
+        expect(assigns(:posts)).to include post
+        expect(assigns(:posts)).to_not include private_post
+        expect(assigns(:posts)).to_not include topicless_post
+      end
     end
 
-    it 'does not return posts without a topic for json' do
-      xhr :get, :latest, format: :json
-      expect(response).to be_success
-      json = ::JSON.parse(response.body)
-      post_ids = json['latest_posts'].map { |p| p['id'] }
-      expect(post_ids).to include post.id
-      expect(post_ids).to_not include topicless_post.id
-    end
+    context 'private posts' do
+      before do
+        Guardian.any_instance.expects(:can_see?).with(private_post).returns(true)
+      end
 
-    it 'does not return posts without a topic for rss' do
-      xhr :get, :latest, format: :rss
-      expect(response).to be_success
-      expect(assigns(:posts)).to include post
-      expect(assigns(:posts)).to_not include topicless_post
+      it 'returns private posts for json' do
+        xhr :get, :latest, id: "private_posts", format: :json
+        expect(response).to be_success
+        json = ::JSON.parse(response.body)
+        post_ids = json['private_posts'].map { |p| p['id'] }
+        expect(post_ids).to include private_post.id
+        expect(post_ids).to_not include post.id
+      end
+
+      it 'returns private posts for rss' do
+        xhr :get, :latest, id: "private_posts", format: :rss
+        expect(response).to be_success
+        expect(assigns(:posts)).to include private_post
+        expect(assigns(:posts)).to_not include post
+      end
     end
   end