Commit b83780786c4695c818f79fc8800c6636a74b000b

Authored by Victor Costa
2 parents 9176e687 8e288dc4

Merge branch 'api' into staging

Conflicts:
	lib/noosfero/api/v1/articles.rb
	test/unit/api/articles_test.rb
@@ -23,6 +23,10 @@ gem 'eita-jrails', '~> 0.9.5', require: 'jrails' @@ -23,6 +23,10 @@ gem 'eita-jrails', '~> 0.9.5', require: 'jrails'
23 # API dependencies 23 # API dependencies
24 gem 'grape', '~> 0.12' 24 gem 'grape', '~> 0.12'
25 gem 'grape-entity' 25 gem 'grape-entity'
  26 +gem 'grape-swagger'
  27 +gem 'swagger-ui_rails'
  28 +gem 'kramdown'
  29 +
26 #FIXME Get the Grape Loggin from master yo solve this issue https://github.com/intridea/grape/issues/1059 30 #FIXME Get the Grape Loggin from master yo solve this issue https://github.com/intridea/grape/issues/1059
27 #We have to remove this commit referenve code when update the next release of grape_logging. Actualy we are using (1.1.2) 31 #We have to remove this commit referenve code when update the next release of grape_logging. Actualy we are using (1.1.2)
28 gem 'grape_logging', :git => 'https://github.com/aceunreal/grape_logging.git', :ref => 'f1755ae' 32 gem 'grape_logging', :git => 'https://github.com/aceunreal/grape_logging.git', :ref => 'f1755ae'
app/controllers/api_docs_controller.rb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +class ApiDocsController < ApplicationController
  2 + layout :api_layout
  3 +
  4 + private
  5 + def api_layout
  6 + params[:controller]
  7 + end
  8 +end
app/views/api_docs/index.html.erb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<%= render 'swagger_ui/swagger_ui', discovery_url: '/api/v1/api_docs' %>
app/views/layouts/api_docs.html.erb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 + <head>
  4 + <meta charset="utf-8">
  5 + <title></title>
  6 + <%= stylesheet_link_tag 'swagger-ui' %>
  7 + <%= javascript_include_tag 'swagger-ui' %>
  8 + </head>
  9 + <body>
  10 + <div id="content">
  11 + <%= yield %>
  12 + </div>
  13 + </body>
  14 +</html>
config/routes.rb
@@ -23,6 +23,7 @@ Noosfero::Application.routes.draw do @@ -23,6 +23,7 @@ Noosfero::Application.routes.draw do
23 23
24 match 'site(/:action)', :controller => 'home' 24 match 'site(/:action)', :controller => 'home'
25 match 'api(/:action)', :controller => 'api' 25 match 'api(/:action)', :controller => 'api'
  26 + match 'api_docs(/:action)', :controller => 'api_docs'
26 27
27 match 'images(/*stuff)' => 'not_found#nothing' 28 match 'images(/*stuff)' => 'not_found#nothing'
28 match 'stylesheets(/*stuff)' => 'not_found#nothing' 29 match 'stylesheets(/*stuff)' => 'not_found#nothing'
lib/noosfero/api/api.rb
@@ -54,6 +54,8 @@ module Noosfero @@ -54,6 +54,8 @@ module Noosfero
54 54
55 mount Session 55 mount Session
56 56
  57 + add_swagger_documentation api_version: 'v1', mount_path: '/api_docs', markdown: GrapeSwagger::Markdown::KramdownAdapter unless Rails.env.production?
  58 +
57 # hook point which allow plugins to add Grape::API extensions to API::API 59 # hook point which allow plugins to add Grape::API extensions to API::API
58 #finds for plugins which has api mount points classes defined (the class should extends Grape::API) 60 #finds for plugins which has api mount points classes defined (the class should extends Grape::API)
59 @plugins = Noosfero::Plugin.all.map { |p| p.constantize } 61 @plugins = Noosfero::Plugin.all.map { |p| p.constantize }
lib/noosfero/api/entities.rb
@@ -64,7 +64,7 @@ module Noosfero @@ -64,7 +64,7 @@ module Noosfero
64 64
65 class Person < Profile 65 class Person < Profile
66 root 'people', 'person' 66 root 'people', 'person'
67 - expose :user, :using => UserBasic 67 + expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' }
68 end 68 end
69 69
70 class Enterprise < Profile 70 class Enterprise < Profile
@@ -80,11 +80,11 @@ module Noosfero @@ -80,11 +80,11 @@ module Noosfero
80 root 'articles', 'article' 80 root 'articles', 'article'
81 expose :id 81 expose :id
82 expose :body 82 expose :body
83 - expose :abstract 83 + expose :abstract, documentation: {type: 'String', desc: 'Teaser of the body'}
84 expose :created_at, :format_with => :timestamp 84 expose :created_at, :format_with => :timestamp
85 expose :title, :documentation => {:type => "String", :desc => "Title of the article"} 85 expose :title, :documentation => {:type => "String", :desc => "Title of the article"}
86 - expose :created_by, :as => :author, :using => Profile  
87 - expose :profile, :using => Profile 86 + expose :created_by, :as => :author, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile author that create the article'}
  87 + expose :profile, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile associated with the article'}
88 expose :categories, :using => Category 88 expose :categories, :using => Category
89 expose :image, :using => Image 89 expose :image, :using => Image
90 #TODO Apply vote stuff in core and make this test 90 #TODO Apply vote stuff in core and make this test
@@ -94,7 +94,7 @@ module Noosfero @@ -94,7 +94,7 @@ module Noosfero
94 expose :position 94 expose :position
95 expose :hits 95 expose :hits
96 expose :start_date 96 expose :start_date
97 - expose :end_date 97 + expose :end_date, :documentation => {type: 'DateTime', desc: 'The date of finish of the article'}
98 expose :tag_list 98 expose :tag_list
99 expose :children_count 99 expose :children_count
100 end 100 end
@@ -105,6 +105,7 @@ module Noosfero @@ -105,6 +105,7 @@ module Noosfero
105 expose :children, using: ArticleBase do |article, options| 105 expose :children, using: ArticleBase do |article, options|
106 article.children.limit(Noosfero::API::V1::Articles::MAX_PER_PAGE) 106 article.children.limit(Noosfero::API::V1::Articles::MAX_PER_PAGE)
107 end 107 end
  108 + expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
108 end 109 end
109 110
110 class Comment < Entity 111 class Comment < Entity
@@ -134,7 +135,7 @@ module Noosfero @@ -134,7 +135,7 @@ module Noosfero
134 end 135 end
135 136
136 class UserLogin < User 137 class UserLogin < User
137 - expose :private_token 138 + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}
138 end 139 end
139 140
140 class Task < Entity 141 class Task < Entity
lib/noosfero/api/v1/articles.rb
@@ -21,6 +21,19 @@ module Noosfero @@ -21,6 +21,19 @@ module Noosfero
21 # Example Request: 21 # Example Request:
22 # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317 22 # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317
23 23
  24 + desc 'Return all articles of all kinds' do
  25 + detail 'Get all articles filtered by fields in query params'
  26 + params Noosfero::API::Entities::Article.documentation
  27 + success Noosfero::API::Entities::Article
  28 + failure [[403, 'Forbidden']]
  29 + named 'ArticlesList'
  30 + headers [
  31 + 'Per-Page' => {
  32 + description: 'Total number of records',
  33 + required: false
  34 + }
  35 + ]
  36 + end
24 get do 37 get do
25 present_articles(environment) 38 present_articles(environment)
26 end 39 end
@@ -30,8 +43,14 @@ module Noosfero @@ -30,8 +43,14 @@ module Noosfero
30 present_articles(current_person, 'following_articles') 43 present_articles(current_person, 'following_articles')
31 end 44 end
32 45
33 - desc "Return the article id"  
34 - get ':id' do 46 + desc "Return one article by id" do
  47 + detail 'Get only one article by id. If not found the "forbidden" http error is showed'
  48 + params Noosfero::API::Entities::Article.documentation
  49 + success Noosfero::API::Entities::Article
  50 + failure [[403, 'Forbidden']]
  51 + named 'ArticleById'
  52 + end
  53 + get ':id', requirements: {id: /[0-9]+/} do
35 present_article(environment) 54 present_article(environment)
36 end 55 end
37 56
@@ -42,6 +61,12 @@ module Noosfero @@ -42,6 +61,12 @@ module Noosfero
42 present article, :with => Entities::Article, :fields => params[:fields] 61 present article, :with => Entities::Article, :fields => params[:fields]
43 end 62 end
44 63
  64 + desc 'Report a abuse and/or violent content in a article by id' do
  65 + detail 'Submit a abuse (in general, a content violation) report about a specific article'
  66 + params Noosfero::API::Entities::Article.documentation
  67 + failure [[400, 'Bad Request']]
  68 + named 'ArticleReportAbuse'
  69 + end
45 post ':id/report_abuse' do 70 post ':id/report_abuse' do
46 article = find_article(environment.articles, params[:id]) 71 article = find_article(environment.articles, params[:id])
47 profile = article.profile 72 profile = article.profile
@@ -70,14 +95,23 @@ module Noosfero @@ -70,14 +95,23 @@ module Noosfero
70 95
71 end 96 end
72 97
73 - desc "Returns the total followers for the article" 98 + desc "Returns the total followers for the article" do
  99 + detail 'Get the followers of a specific article by id'
  100 + failure [[403, 'Forbidden']]
  101 + named 'ArticleFollowers'
  102 + end
74 get ':id/followers' do 103 get ':id/followers' do
75 article = find_article(environment.articles, params[:id]) 104 article = find_article(environment.articles, params[:id])
76 total = article.person_followers.count 105 total = article.person_followers.count
77 {:total_followers => total} 106 {:total_followers => total}
78 end 107 end
79 108
80 - desc "Add a follower for the article" 109 + desc "Add a follower for the article" do
  110 + detail 'Add the current user identified by private token, like a follower of a article'
  111 + params Noosfero::API::Entities::UserLogin.documentation
  112 + failure [[401, 'Unauthorized']]
  113 + named 'ArticleFollow'
  114 + end
81 post ':id/follow' do 115 post ':id/follow' do
82 authenticate! 116 authenticate!
83 article = find_article(environment.articles, params[:id]) 117 article = find_article(environment.articles, params[:id])
@@ -92,6 +126,12 @@ module Noosfero @@ -92,6 +126,12 @@ module Noosfero
92 end 126 end
93 end 127 end
94 128
  129 + desc 'Perform a vote on a article by id' do
  130 + detail 'Vote on a specific article with values: 1 (if you like) or -1 (if not)'
  131 + params Noosfero::API::Entities::UserLogin.documentation
  132 + failure [[401,'Unauthorized']]
  133 + named 'ArticleVote'
  134 + end
95 post ':id/vote' do 135 post ':id/vote' do
96 authenticate! 136 authenticate!
97 value = (params[:value] || 1).to_i 137 value = (params[:value] || 1).to_i
@@ -102,13 +142,20 @@ module Noosfero @@ -102,13 +142,20 @@ module Noosfero
102 {:vote => vote.save} 142 {:vote => vote.save}
103 end 143 end
104 144
  145 + desc 'Return the children of a article identified by id' do
  146 + detail 'Get all children articles of a specific article'
  147 + params Noosfero::API::Entities::Article.documentation
  148 + failure [[403, 'Forbidden']]
  149 + named 'ArticleChildren'
  150 + end
105 get ':id/children' do 151 get ':id/children' do
106 article = find_article(environment.articles, params[:id]) 152 article = find_article(environment.articles, params[:id])
107 153
108 #TODO make tests for this situation 154 #TODO make tests for this situation
109 votes_order = params.delete(:order) if params[:order]=='votes_score' 155 votes_order = params.delete(:order) if params[:order]=='votes_score'
  156 + articles = select_filtered_collection_of(article, 'children', params)
  157 + articles = articles.display_filter(current_person, article.profile)
110 158
111 - articles = find_articles(article, 'children')  
112 159
113 #TODO make tests for this situation 160 #TODO make tests for this situation
114 if votes_order 161 if votes_order
@@ -119,6 +166,13 @@ module Noosfero @@ -119,6 +166,13 @@ module Noosfero
119 present articles, :with => Entities::Article, :fields => params[:fields] 166 present articles, :with => Entities::Article, :fields => params[:fields]
120 end 167 end
121 168
  169 + desc 'Return one child of a article identified by id' do
  170 + detail 'Get a child of a specific article'
  171 + params Noosfero::API::Entities::Article.documentation
  172 + success Noosfero::API::Entities::Article
  173 + failure [[403, 'Forbidden']]
  174 + named 'ArticleChild'
  175 + end
122 get ':id/children/:child_id' do 176 get ':id/children/:child_id' do
123 article = find_article(environment.articles, params[:id]) 177 article = find_article(environment.articles, params[:id])
124 child = find_article(article.children, params[:child_id]) 178 child = find_article(article.children, params[:child_id])
@@ -126,6 +180,13 @@ module Noosfero @@ -126,6 +180,13 @@ module Noosfero
126 present child, :with => Entities::Article, :fields => params[:fields] 180 present child, :with => Entities::Article, :fields => params[:fields]
127 end 181 end
128 182
  183 + desc 'Suggest a article to another profile' do
  184 + detail 'Suggest a article to another profile (person, community...)'
  185 + params Noosfero::API::Entities::Article.documentation
  186 + success Noosfero::API::Entities::Task
  187 + failure [[401,'Unauthorized']]
  188 + named 'ArticleSuggest'
  189 + end
129 post ':id/children/suggest' do 190 post ':id/children/suggest' do
130 authenticate! 191 authenticate!
131 parent_article = environment.articles.find(params[:id]) 192 parent_article = environment.articles.find(params[:id])
@@ -144,6 +205,13 @@ module Noosfero @@ -144,6 +205,13 @@ module Noosfero
144 205
145 # Example Request: 206 # Example Request:
146 # POST api/v1/articles/:id/children?private_token=234298743290432&article[name]=title&article[body]=body 207 # POST api/v1/articles/:id/children?private_token=234298743290432&article[name]=title&article[body]=body
  208 + desc 'Add a child article to a parent identified by id' do
  209 + detail 'Create a new article and associate to a parent'
  210 + params Noosfero::API::Entities::Article.documentation
  211 + success Noosfero::API::Entities::Article
  212 + failure [[401,'Unauthorized']]
  213 + named 'ArticleAddChild'
  214 + end
147 post ':id/children' do 215 post ':id/children' do
148 authenticate! 216 authenticate!
149 parent_article = environment.articles.find(params[:id]) 217 parent_article = environment.articles.find(params[:id])
@@ -173,11 +241,37 @@ module Noosfero @@ -173,11 +241,37 @@ module Noosfero
173 resource kind.pluralize.to_sym do 241 resource kind.pluralize.to_sym do
174 segment "/:#{kind}_id" do 242 segment "/:#{kind}_id" do
175 resource :articles do 243 resource :articles do
  244 +
  245 + desc "Return all articles associate with a profile of type #{kind}" do
  246 + detail 'Get a list of articles of a profile'
  247 + params Noosfero::API::Entities::Article.documentation
  248 + success Noosfero::API::Entities::Article
  249 + failure [[403, 'Forbidden']]
  250 + named 'ArticlesOfProfile'
  251 + end
176 get do 252 get do
177 profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) 253 profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
178 - present_articles(profile) 254 +
  255 + if params[:path].present?
  256 + article = profile.articles.find_by_path(params[:path])
  257 + if !article || !article.display_to?(current_person)
  258 + article = forbidden!
  259 + end
  260 +
  261 + present article, :with => Entities::Article, :fields => params[:fields]
  262 + else
  263 +
  264 + present_articles(profile)
  265 + end
179 end 266 end
180 267
  268 + desc "Return a article associate with a profile of type #{kind}" do
  269 + detail 'Get only one article of a profile'
  270 + params Noosfero::API::Entities::Article.documentation
  271 + success Noosfero::API::Entities::Article
  272 + failure [[403, 'Forbidden']]
  273 + named 'ArticleOfProfile'
  274 + end
181 get ':id' do 275 get ':id' do
182 profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) 276 profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
183 present_article(profile) 277 present_article(profile)
@@ -185,6 +279,13 @@ module Noosfero @@ -185,6 +279,13 @@ module Noosfero
185 279
186 # Example Request: 280 # Example Request:
187 # POST api/v1/{people,communities,enterprises}/:asset_id/articles?private_token=234298743290432&article[name]=title&article[body]=body 281 # POST api/v1/{people,communities,enterprises}/:asset_id/articles?private_token=234298743290432&article[name]=title&article[body]=body
  282 + desc "Add a new article associated with a profile of type #{kind}" do
  283 + detail 'Create a new article and associate with a profile'
  284 + params Noosfero::API::Entities::Article.documentation
  285 + success Noosfero::API::Entities::Article
  286 + failure [[403, 'Forbidden']]
  287 + named 'ArticleCreateToProfile'
  288 + end
188 post do 289 post do
189 profile = environment.send(kind.pluralize).find(params["#{kind}_id"]) 290 profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
190 post_article(profile, params) 291 post_article(profile, params)
test/unit/api/articles_test.rb
@@ -91,6 +91,41 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -91,6 +91,41 @@ class ArticlesTest &lt; ActiveSupport::TestCase
91 assert_not_includes json['articles'].map {|a| a['id']}, child.id 91 assert_not_includes json['articles'].map {|a| a['id']}, child.id
92 end 92 end
93 93
  94 + should 'follow a article identified by id' do
  95 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  96 + post "/api/v1/articles/#{article.id}/follow?#{params.to_query}"
  97 + json = JSON.parse(last_response.body)
  98 +
  99 + assert_not_equal 401, last_response.status
  100 + assert_equal true, json['success']
  101 + end
  102 +
  103 + should 'return the followers of a article identified by id' do
  104 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  105 +
  106 + article_follower = ArticleFollower.new
  107 + article_follower.article = article
  108 + article_follower.person = @person
  109 + article_follower.save!
  110 +
  111 + get "/api/v1/articles/#{article.id}/followers?"
  112 + json = JSON.parse(last_response.body)
  113 +
  114 + assert_equal 200, last_response.status
  115 + assert_equal 1, json['total_followers']
  116 + end
  117 +
  118 + should 'perform a vote in a article identified by id' do
  119 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  120 + @params[:value] = 1
  121 +
  122 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  123 + json = JSON.parse(last_response.body)
  124 +
  125 + assert_not_equal 401, last_response.status
  126 + assert_equal true, json['vote']
  127 + end
  128 +
94 expose_attributes = %w(id body abstract created_at title author profile categories image votes_for votes_against setting position hits start_date end_date tag_list parent children children_count) 129 expose_attributes = %w(id body abstract created_at title author profile categories image votes_for votes_against setting position hits start_date end_date tag_list parent children children_count)
95 130
96 expose_attributes.each do |attr| 131 expose_attributes.each do |attr|
@@ -134,6 +169,29 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -134,6 +169,29 @@ class ArticlesTest &lt; ActiveSupport::TestCase
134 json = JSON.parse(last_response.body) 169 json = JSON.parse(last_response.body)
135 assert_not_includes json['articles'].map {|a| a['id']}, article.id 170 assert_not_includes json['articles'].map {|a| a['id']}, article.id
136 end 171 end
  172 +
  173 + should "return article by #{kind} and path" do
  174 + profile = fast_create(kind.camelcase.constantize, :environment_id => environment.id)
  175 + parent_article = Folder.create!(:profile => profile, :name => "Parent Folder")
  176 + article = Article.create!(:profile => profile, :name => "Some thing", :parent => parent_article)
  177 +
  178 + params[:path] = parent_article.slug+'/'+article.slug
  179 + get "/api/v1/#{kind.pluralize}/#{profile.id}/articles?#{params.to_query}"
  180 + json = JSON.parse(last_response.body)
  181 + assert_equal article.id, json["article"]["id"]
  182 + end
  183 +
  184 + should "not return article by #{kind} and path if user has no permission to view it" do
  185 + profile = fast_create(kind.camelcase.constantize, :environment_id => environment.id)
  186 + parent_article = Folder.create!(:profile => profile, :name => "Parent Folder")
  187 + article = Article.create!(:profile => profile, :name => "Some thing", :parent => parent_article, :published => false)
  188 +
  189 + assert !article.published?
  190 +
  191 + params[:path] = parent_article.slug+'/'+article.slug
  192 + get "/api/v1/#{kind.pluralize}/#{profile.id}/articles?#{params.to_query}"
  193 + assert_equal 403, last_response.status
  194 + end
137 end 195 end
138 196
139 ############################# 197 #############################
test/unit/api/test_helper.rb
@@ -17,6 +17,11 @@ class ActiveSupport::TestCase @@ -17,6 +17,11 @@ class ActiveSupport::TestCase
17 post "/api/v1/login?login=testapi&password=testapi" 17 post "/api/v1/login?login=testapi&password=testapi"
18 json = JSON.parse(last_response.body) 18 json = JSON.parse(last_response.body)
19 @private_token = json["private_token"] 19 @private_token = json["private_token"]
  20 + unless @private_token
  21 + @user.generate_private_token!
  22 + @private_token = @user.private_token
  23 + end
  24 +
20 @params = {:private_token => @private_token} 25 @params = {:private_token => @private_token}
21 end 26 end
22 attr_accessor :private_token, :user, :person, :params, :environment 27 attr_accessor :private_token, :user, :person, :params, :environment