Commit 100df3575f11f2c551eb1e950440e3f648f19b98

Authored by Marcos Pereira
1 parent ea20a3dc

API improvements

  - changes pagination to use api-pagination gem
  - new endpoints to boxes, activities, and profiles
  - new exposed attributes for some entities
  - other minor changes

  Signed-off-by: Leandro Nunes dos Santos <leandronunes@gmail.com>
  Signed-off-by: Marcos Ronaldo <marcos.rpj2@gmail.com>
  Signed-off-by: Michel Felipe de Oliveira Ferreira <michel.ferreira@serpro.gov.br>
  Signed-off-by: Victor Costa <vfcosta@gmail.com>
@@ -34,10 +34,11 @@ gem &#39;slim&#39; @@ -34,10 +34,11 @@ gem &#39;slim&#39;
34 34
35 # API dependencies 35 # API dependencies
36 gem 'grape', '~> 0.12' 36 gem 'grape', '~> 0.12'
37 -gem 'grape-entity' 37 +gem 'grape-entity', '0.4.8'
38 gem 'grape_logging' 38 gem 'grape_logging'
39 gem 'rack-cors' 39 gem 'rack-cors'
40 gem 'rack-contrib' 40 gem 'rack-contrib'
  41 +gem 'api-pagination', '~> 4.1.1'
41 42
42 # asset pipeline 43 # asset pipeline
43 gem 'uglifier', '>= 1.0.3' 44 gem 'uglifier', '>= 1.0.3'
app/models/profile.rb
@@ -388,6 +388,10 @@ class Profile &lt; ActiveRecord::Base @@ -388,6 +388,10 @@ class Profile &lt; ActiveRecord::Base
388 !profiles.exists? 388 !profiles.exists?
389 end 389 end
390 390
  391 + def self.visible_for_person(person)
  392 + self.all
  393 + end
  394 +
391 validates_presence_of :identifier, :name 395 validates_presence_of :identifier, :name
392 validates_length_of :nickname, :maximum => 16, :allow_nil => true 396 validates_length_of :nickname, :maximum => 16, :allow_nil => true
393 validate :valid_template 397 validate :valid_template
lib/noosfero/api/api.rb
@@ -52,6 +52,9 @@ module Noosfero @@ -52,6 +52,9 @@ module Noosfero
52 mount V1::Environments 52 mount V1::Environments
53 mount V1::Search 53 mount V1::Search
54 mount V1::Contacts 54 mount V1::Contacts
  55 + mount V1::Boxes
  56 + mount V1::Profiles
  57 + mount V1::Activities
55 58
56 mount Session 59 mount Session
57 60
lib/noosfero/api/entities.rb
@@ -79,6 +79,18 @@ module Noosfero @@ -79,6 +79,18 @@ module Noosfero
79 expose :parent_id 79 expose :parent_id
80 end 80 end
81 81
  82 + class Block < Entity
  83 + root 'blocks', 'block'
  84 + expose :id, :type, :settings, :position, :enabled
  85 + expose :mirror, :mirror_block_id, :title
  86 + end
  87 +
  88 + class Box < Entity
  89 + root 'boxes', 'box'
  90 + expose :id, :position
  91 + expose :blocks, :using => Block
  92 + end
  93 +
82 class Profile < Entity 94 class Profile < Entity
83 expose :identifier, :name, :id 95 expose :identifier, :name, :id
84 expose :created_at, :format_with => :timestamp 96 expose :created_at, :format_with => :timestamp
@@ -99,6 +111,7 @@ module Noosfero @@ -99,6 +111,7 @@ module Noosfero
99 end 111 end
100 expose :image, :using => Image 112 expose :image, :using => Image
101 expose :region, :using => Region 113 expose :region, :using => Region
  114 + expose :type
102 end 115 end
103 116
104 class UserBasic < Entity 117 class UserBasic < Entity
@@ -109,6 +122,16 @@ module Noosfero @@ -109,6 +122,16 @@ module Noosfero
109 class Person < Profile 122 class Person < Profile
110 root 'people', 'person' 123 root 'people', 'person'
111 expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' } 124 expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' }
  125 + expose :vote_count
  126 + expose :comments_count do |person, options|
  127 + person.comments.count
  128 + end
  129 + expose :following_articles_count do |person, options|
  130 + person.following_articles.count
  131 + end
  132 + expose :articles_count do |person, options|
  133 + person.articles.count
  134 + end
112 end 135 end
113 136
114 class Enterprise < Profile 137 class Enterprise < Profile
@@ -149,6 +172,8 @@ module Noosfero @@ -149,6 +172,8 @@ module Noosfero
149 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"} 172 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
150 expose :path 173 expose :path
151 expose :followers_count 174 expose :followers_count
  175 + expose :votes_count
  176 + expose :comments_count
152 end 177 end
153 178
154 class Article < ArticleBase 179 class Article < ArticleBase
@@ -189,7 +214,7 @@ module Noosfero @@ -189,7 +214,7 @@ module Noosfero
189 214
190 class UserLogin < User 215 class UserLogin < User
191 root 'users', 'user' 216 root 'users', 'user'
192 - expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'} 217 + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}, if: lambda {|object, options| object.activated? }
193 end 218 end
194 219
195 class Task < Entity 220 class Task < Entity
@@ -207,7 +232,15 @@ module Noosfero @@ -207,7 +232,15 @@ module Noosfero
207 expose :name 232 expose :name
208 end 233 end
209 234
210 - 235 + class Activity < Entity
  236 + root 'activities', 'activity'
  237 + expose :id, :params, :verb, :created_at, :updated_at, :comments_count, :visible
  238 + expose :user, :using => Profile
  239 + expose :target do |activity, opts|
  240 + type_map = {Profile => ::Profile, ArticleBase => ::Article}.find {|h| activity.target.kind_of?(h.last)}
  241 + type_map.first.represent(activity.target) unless type_map.nil?
  242 + end
  243 + end
211 end 244 end
212 end 245 end
213 end 246 end
lib/noosfero/api/entity.rb
@@ -22,18 +22,4 @@ class Noosfero::API::Entity &lt; Grape::Entity @@ -22,18 +22,4 @@ class Noosfero::API::Entity &lt; Grape::Entity
22 end 22 end
23 end 23 end
24 24
25 - def self.fields_condition(fields)  
26 - lambda do |object, options|  
27 - return true if options[:fields].blank?  
28 - fields.map { |field| options[:fields].include?(field.to_s)}.grep(true).present?  
29 - end  
30 - end  
31 -  
32 - def self.expose(*args, &block)  
33 - hash = args.last.is_a?(Hash) ? args.pop : {}  
34 - hash.merge!({:if => fields_condition(args)}) if hash[:if].blank?  
35 - args << hash  
36 - super  
37 - end  
38 -  
39 end 25 end
lib/noosfero/api/helpers.rb
@@ -5,7 +5,7 @@ require_relative &#39;../../find_by_contents&#39; @@ -5,7 +5,7 @@ require_relative &#39;../../find_by_contents&#39;
5 module API 5 module API
6 module APIHelpers 6 module APIHelpers
7 PRIVATE_TOKEN_PARAM = :private_token 7 PRIVATE_TOKEN_PARAM = :private_token
8 - DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type, :author_id] 8 + DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type, :author_id, :identifier]
9 9
10 include SanitizeParams 10 include SanitizeParams
11 include Noosfero::Plugin::HotSpot 11 include Noosfero::Plugin::HotSpot
@@ -38,6 +38,20 @@ require_relative &#39;../../find_by_contents&#39; @@ -38,6 +38,20 @@ require_relative &#39;../../find_by_contents&#39;
38 @environment 38 @environment
39 end 39 end
40 40
  41 + def present_partial(model, options)
  42 + if(params[:fields].present?)
  43 + begin
  44 + fields = JSON.parse(params[:fields])
  45 + if fields.present?
  46 + options.merge!(fields.symbolize_keys.slice(:only, :except))
  47 + end
  48 + rescue
  49 + options[:only] = Array.wrap(params[:fields])
  50 + end
  51 + end
  52 + present model, options
  53 + end
  54 +
41 include FindByContents 55 include FindByContents
42 56
43 #################################################################### 57 ####################################################################
@@ -87,7 +101,7 @@ require_relative &#39;../../find_by_contents&#39; @@ -87,7 +101,7 @@ require_relative &#39;../../find_by_contents&#39;
87 def post_article(asset, params) 101 def post_article(asset, params)
88 return forbidden! unless current_person.can_post_content?(asset) 102 return forbidden! unless current_person.can_post_content?(asset)
89 103
90 - klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] 104 + klass_type= params[:content_type].nil? ? TinyMceArticle.name : params[:content_type]
91 return forbidden! unless ARTICLE_TYPES.include?(klass_type) 105 return forbidden! unless ARTICLE_TYPES.include?(klass_type)
92 106
93 article = klass_type.constantize.new(params[:article]) 107 article = klass_type.constantize.new(params[:article])
@@ -98,12 +112,12 @@ require_relative &#39;../../find_by_contents&#39; @@ -98,12 +112,12 @@ require_relative &#39;../../find_by_contents&#39;
98 if !article.save 112 if !article.save
99 render_api_errors!(article.errors.full_messages) 113 render_api_errors!(article.errors.full_messages)
100 end 114 end
101 - present article, :with => Entities::Article, :fields => params[:fields] 115 + present_partial article, :with => Entities::Article
102 end 116 end
103 117
104 def present_article(asset) 118 def present_article(asset)
105 article = find_article(asset.articles, params[:id]) 119 article = find_article(asset.articles, params[:id])
106 - present article, :with => Entities::Article, :fields => params[:fields] 120 + present_partial article, :with => Entities::Article
107 end 121 end
108 122
109 def present_articles_for_asset(asset, method = 'articles') 123 def present_articles_for_asset(asset, method = 'articles')
@@ -112,7 +126,7 @@ require_relative &#39;../../find_by_contents&#39; @@ -112,7 +126,7 @@ require_relative &#39;../../find_by_contents&#39;
112 end 126 end
113 127
114 def present_articles(articles) 128 def present_articles(articles)
115 - present articles, :with => Entities::Article, :fields => params[:fields] 129 + present_partial paginate(articles), :with => Entities::Article
116 end 130 end
117 131
118 def find_articles(asset, method = 'articles') 132 def find_articles(asset, method = 'articles')
@@ -142,19 +156,19 @@ require_relative &#39;../../find_by_contents&#39; @@ -142,19 +156,19 @@ require_relative &#39;../../find_by_contents&#39;
142 if !task.save 156 if !task.save
143 render_api_errors!(task.errors.full_messages) 157 render_api_errors!(task.errors.full_messages)
144 end 158 end
145 - present task, :with => Entities::Task, :fields => params[:fields] 159 + present_partial task, :with => Entities::Task
146 end 160 end
147 161
148 def present_task(asset) 162 def present_task(asset)
149 task = find_task(asset, params[:id]) 163 task = find_task(asset, params[:id])
150 - present task, :with => Entities::Task, :fields => params[:fields] 164 + present_partial task, :with => Entities::Task
151 end 165 end
152 166
153 def present_tasks(asset) 167 def present_tasks(asset)
154 tasks = select_filtered_collection_of(asset, 'tasks', params) 168 tasks = select_filtered_collection_of(asset, 'tasks', params)
155 tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)} 169 tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)}
156 return forbidden! if tasks.empty? && !current_person.has_permission?(:perform_task, asset) 170 return forbidden! if tasks.empty? && !current_person.has_permission?(:perform_task, asset)
157 - present tasks, :with => Entities::Task, :fields => params[:fields] 171 + present_partial tasks, :with => Entities::Task
158 end 172 end
159 173
160 def make_conditions_with_parameter(params = {}) 174 def make_conditions_with_parameter(params = {})
@@ -194,15 +208,6 @@ require_relative &#39;../../find_by_contents&#39; @@ -194,15 +208,6 @@ require_relative &#39;../../find_by_contents&#39;
194 return order 208 return order
195 end 209 end
196 210
197 - def make_page_number_with_parameters(params)  
198 - params[:page] || 1  
199 - end  
200 -  
201 - def make_per_page_with_parameters(params)  
202 - params[:per_page] ||= limit  
203 - params[:per_page].to_i  
204 - end  
205 -  
206 def make_timestamp_with_parameters_and_method(params, method) 211 def make_timestamp_with_parameters_and_method(params, method)
207 timestamp = nil 212 timestamp = nil
208 if params[:timestamp] 213 if params[:timestamp]
@@ -236,17 +241,17 @@ require_relative &#39;../../find_by_contents&#39; @@ -236,17 +241,17 @@ require_relative &#39;../../find_by_contents&#39;
236 def select_filtered_collection_of(object, method, params) 241 def select_filtered_collection_of(object, method, params)
237 conditions = make_conditions_with_parameter(params) 242 conditions = make_conditions_with_parameter(params)
238 order = make_order_with_parameters(object,method,params) 243 order = make_order_with_parameters(object,method,params)
239 - page_number = make_page_number_with_parameters(params)  
240 - per_page = make_per_page_with_parameters(params)  
241 timestamp = make_timestamp_with_parameters_and_method(params, method) 244 timestamp = make_timestamp_with_parameters_and_method(params, method)
242 245
243 objects = object.send(method) 246 objects = object.send(method)
244 objects = by_reference(objects, params) 247 objects = by_reference(objects, params)
245 objects = by_categories(objects, params) 248 objects = by_categories(objects, params)
246 249
247 - objects = objects.where(conditions).where(timestamp).page(page_number).per_page(per_page).reorder(order) 250 + objects = objects.where(conditions).where(timestamp).reorder(order)
248 251
249 - objects 252 + params[:page] ||= 1
  253 + params[:per_page] ||= limit
  254 + paginate(objects)
250 end 255 end
251 256
252 def authenticate! 257 def authenticate!
lib/noosfero/api/session.rb
@@ -24,6 +24,14 @@ module Noosfero @@ -24,6 +24,14 @@ module Noosfero
24 present user, :with => Entities::UserLogin, :current_person => current_person 24 present user, :with => Entities::UserLogin, :current_person => current_person
25 end 25 end
26 26
  27 + post "/login_from_cookie" do
  28 + return unauthorized! if cookies[:auth_token].blank?
  29 + user = User.where(remember_token: cookies[:auth_token]).first
  30 + return unauthorized! unless user && user.activated?
  31 + @current_user = user
  32 + present user, :with => Entities::UserLogin, :current_person => current_person
  33 + end
  34 +
27 # Create user. 35 # Create user.
28 # 36 #
29 # Parameters: 37 # Parameters:
lib/noosfero/api/v1/activities.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 + class Activities < Grape::API
  5 + before { authenticate! }
  6 +
  7 + resource :profiles do
  8 +
  9 + get ':id/activities' do
  10 + profile = environment.profiles
  11 + profile = profile.visible_for_person(current_person) if profile.respond_to?(:visible_for_person)
  12 + profile = profile.find_by_id(params[:id])
  13 + activities = profile.activities.map(&:activity)
  14 + present activities, :with => Entities::Activity, :current_person => current_person
  15 + end
  16 + end
  17 + end
  18 + end
  19 + end
  20 +end
lib/noosfero/api/v1/articles.rb
@@ -5,8 +5,11 @@ module Noosfero @@ -5,8 +5,11 @@ module Noosfero
5 5
6 ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} 6 ARTICLE_TYPES = Article.descendants.map{|a| a.to_s}
7 7
  8 + MAX_PER_PAGE = 50
  9 +
8 resource :articles do 10 resource :articles do
9 11
  12 + paginate max_per_page: MAX_PER_PAGE
10 # Collect articles 13 # Collect articles
11 # 14 #
12 # Parameters: 15 # Parameters:
@@ -49,7 +52,7 @@ module Noosfero @@ -49,7 +52,7 @@ module Noosfero
49 article = environment.articles.find(params[:id]) 52 article = environment.articles.find(params[:id])
50 return forbidden! unless article.allow_edit?(current_person) 53 return forbidden! unless article.allow_edit?(current_person)
51 article.update_attributes!(params[:article]) 54 article.update_attributes!(params[:article])
52 - present article, :with => Entities::Article, :fields => params[:fields] 55 + present_partial article, :with => Entities::Article
53 end 56 end
54 57
55 desc 'Report a abuse and/or violent content in a article by id' do 58 desc 'Report a abuse and/or violent content in a article by id' do
@@ -93,7 +96,7 @@ module Noosfero @@ -93,7 +96,7 @@ module Noosfero
93 end 96 end
94 #FIXME refactor this method 97 #FIXME refactor this method
95 get 'voted_by_me' do 98 get 'voted_by_me' do
96 - present_articles(current_person.votes.collect(&:voteable)) 99 + present_articles(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable))
97 end 100 end
98 101
99 desc 'Perform a vote on a article by id' do 102 desc 'Perform a vote on a article by id' do
@@ -108,8 +111,12 @@ module Noosfero @@ -108,8 +111,12 @@ module Noosfero
108 # FIXME verify allowed values 111 # FIXME verify allowed values
109 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value) 112 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
110 article = find_article(environment.articles, params[:id]) 113 article = find_article(environment.articles, params[:id])
111 - vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)  
112 - {:vote => vote.save} 114 + begin
  115 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  116 + {:vote => vote.save!}
  117 + rescue ActiveRecord::RecordInvalid => e
  118 + render_api_error!(e.message, 400)
  119 + end
113 end 120 end
114 121
115 desc "Returns the total followers for the article" do 122 desc "Returns the total followers for the article" do
@@ -123,6 +130,11 @@ module Noosfero @@ -123,6 +130,11 @@ module Noosfero
123 {:total_followers => total} 130 {:total_followers => total}
124 end 131 end
125 132
  133 + desc "Return the articles followed by me"
  134 + get 'followed_by_me' do
  135 + present_articles_for_asset(current_person, 'following_articles')
  136 + end
  137 +
126 desc "Add a follower for the article" do 138 desc "Add a follower for the article" do
127 detail 'Add the current user identified by private token, like a follower of a article' 139 detail 'Add the current user identified by private token, like a follower of a article'
128 params Noosfero::API::Entities::UserLogin.documentation 140 params Noosfero::API::Entities::UserLogin.documentation
@@ -150,6 +162,7 @@ module Noosfero @@ -150,6 +162,7 @@ module Noosfero
150 named 'ArticleChildren' 162 named 'ArticleChildren'
151 end 163 end
152 164
  165 + paginate per_page: MAX_PER_PAGE, max_per_page: MAX_PER_PAGE
153 get ':id/children' do 166 get ':id/children' do
154 article = find_article(environment.articles, params[:id]) 167 article = find_article(environment.articles, params[:id])
155 168
@@ -177,7 +190,7 @@ module Noosfero @@ -177,7 +190,7 @@ module Noosfero
177 article = find_article(environment.articles, params[:id]) 190 article = find_article(environment.articles, params[:id])
178 child = find_article(article.children, params[:child_id]) 191 child = find_article(article.children, params[:child_id])
179 child.hit 192 child.hit
180 - present child, :with => Entities::Article, :fields => params[:fields] 193 + present_partial child, :with => Entities::Article
181 end 194 end
182 195
183 desc 'Suggest a article to another profile' do 196 desc 'Suggest a article to another profile' do
@@ -200,7 +213,7 @@ module Noosfero @@ -200,7 +213,7 @@ module Noosfero
200 unless suggest_article.save 213 unless suggest_article.save
201 render_api_errors!(suggest_article.article_object.errors.full_messages) 214 render_api_errors!(suggest_article.article_object.errors.full_messages)
202 end 215 end
203 - present suggest_article, :with => Entities::Task, :fields => params[:fields] 216 + present_partial suggest_article, :with => Entities::Task
204 end 217 end
205 218
206 # Example Request: 219 # Example Request:
@@ -231,12 +244,21 @@ module Noosfero @@ -231,12 +244,21 @@ module Noosfero
231 if !article.save 244 if !article.save
232 render_api_errors!(article.errors.full_messages) 245 render_api_errors!(article.errors.full_messages)
233 end 246 end
234 - present article, :with => Entities::Article, :fields => params[:fields] 247 + present_partial article, :with => Entities::Article
235 end 248 end
236 249
237 end 250 end
238 251
239 - kinds = %w[community person enterprise] 252 + resource :profiles do
  253 + get ':id/home_page' do
  254 + profiles = environment.profiles
  255 + profiles = profiles.visible_for_person(current_person)
  256 + profile = profiles.find_by_id(params[:id])
  257 + present_partial profile.home_page, :with => Entities::Article
  258 + end
  259 + end
  260 +
  261 + kinds = %w[profile community person enterprise]
240 kinds.each do |kind| 262 kinds.each do |kind|
241 resource kind.pluralize.to_sym do 263 resource kind.pluralize.to_sym do
242 segment "/:#{kind}_id" do 264 segment "/:#{kind}_id" do
@@ -258,7 +280,7 @@ module Noosfero @@ -258,7 +280,7 @@ module Noosfero
258 article = forbidden! 280 article = forbidden!
259 end 281 end
260 282
261 - present article, :with => Entities::Article, :fields => params[:fields] 283 + present_partial article, :with => Entities::Article
262 else 284 else
263 285
264 present_articles_for_asset(profile) 286 present_articles_for_asset(profile)
lib/noosfero/api/v1/boxes.rb 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 +
  5 + class Boxes < Grape::API
  6 +
  7 + kinds = %w[profile community person enterprise]
  8 + kinds.each do |kind|
  9 +
  10 + resource kind.pluralize.to_sym do
  11 +
  12 + segment "/:#{kind}_id" do
  13 + resource :boxes do
  14 + get do
  15 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  16 + present profile.boxes, :with => Entities::Box
  17 + end
  18 + end
  19 + end
  20 +
  21 + end
  22 +
  23 + end
  24 + end
  25 +
  26 + end
  27 + end
  28 +end
lib/noosfero/api/v1/comments.rb
@@ -31,7 +31,12 @@ module Noosfero @@ -31,7 +31,12 @@ module Noosfero
31 post ":id/comments" do 31 post ":id/comments" do
32 article = find_article(environment.articles, params[:id]) 32 article = find_article(environment.articles, params[:id])
33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article) 33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
34 - present Comment.create(options), :with => Entities::Comment, :current_person => current_person 34 + begin
  35 + comment = Comment.create!(options)
  36 + rescue ActiveRecord::RecordInvalid => e
  37 + render_api_error!(e.message, 400)
  38 + end
  39 + present comment, :with => Entities::Comment, :current_person => current_person
35 end 40 end
36 end 41 end
37 42
lib/noosfero/api/v1/people.rb
@@ -4,9 +4,12 @@ module Noosfero @@ -4,9 +4,12 @@ module Noosfero
4 class People < Grape::API 4 class People < Grape::API
5 before { authenticate! } 5 before { authenticate! }
6 6
  7 + MAX_PER_PAGE = 50
  8 +
7 desc 'API Root' 9 desc 'API Root'
8 10
9 resource :people do 11 resource :people do
  12 + paginate max_per_page: MAX_PER_PAGE
10 13
11 # -- A note about privacy -- 14 # -- A note about privacy --
12 # We wold find people by location, but we must test if the related 15 # We wold find people by location, but we must test if the related
@@ -33,12 +36,12 @@ module Noosfero @@ -33,12 +36,12 @@ module Noosfero
33 get do 36 get do
34 people = select_filtered_collection_of(environment, 'people', params) 37 people = select_filtered_collection_of(environment, 'people', params)
35 people = people.visible_for_person(current_person) 38 people = people.visible_for_person(current_person)
36 - present people, :with => Entities::Person, :current_person => current_person 39 + present_partial people, :with => Entities::Person, :current_person => current_person
37 end 40 end
38 41
39 desc "Return the logged user information" 42 desc "Return the logged user information"
40 get "/me" do 43 get "/me" do
41 - present current_person, :with => Entities::Person, :current_person => current_person 44 + present_partial current_person, :with => Entities::Person, :current_person => current_person
42 end 45 end
43 46
44 desc "Return the person information" 47 desc "Return the person information"
@@ -105,6 +108,19 @@ module Noosfero @@ -105,6 +108,19 @@ module Noosfero
105 present output 108 present output
106 end 109 end
107 end 110 end
  111 +
  112 + resource :profiles do
  113 + segment '/:profile_id' do
  114 + resource :members do
  115 + paginate max_per_page: MAX_PER_PAGE
  116 + get do
  117 + profile = environment.profiles.find_by_id(params[:profile_id])
  118 + members = select_filtered_collection_of(profile, 'members', params)
  119 + present members, :with => Entities::Person, :current_person => current_person
  120 + end
  121 + end
  122 + end
  123 + end
108 end 124 end
109 end 125 end
110 end 126 end
lib/noosfero/api/v1/profiles.rb 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 + class Profiles < Grape::API
  5 + before { authenticate! }
  6 +
  7 + resource :profiles do
  8 +
  9 + get do
  10 + profiles = select_filtered_collection_of(environment, 'profiles', params)
  11 + profiles = profiles.visible_for_person(current_person)
  12 + profiles = profiles.by_location(params) # Must be the last. May return Exception obj.
  13 + present profiles, :with => Entities::Profile, :current_person => current_person
  14 + end
  15 +
  16 + get ':id' do
  17 + profiles = environment.profiles
  18 + profiles = profiles.visible_for_person(current_person)
  19 + profile = profiles.find_by_id(params[:id])
  20 + present profile, :with => Entities::Profile, :current_person => current_person
  21 + end
  22 + end
  23 + end
  24 + end
  25 + end
  26 +end
lib/noosfero/api/v1/search.rb
@@ -5,6 +5,7 @@ module Noosfero @@ -5,6 +5,7 @@ module Noosfero
5 5
6 resource :search do 6 resource :search do
7 resource :article do 7 resource :article do
  8 + paginate max_per_page: 200
8 get do 9 get do
9 # Security checks 10 # Security checks
10 sanitize_params_hash(params) 11 sanitize_params_hash(params)
@@ -15,24 +16,19 @@ module Noosfero @@ -15,24 +16,19 @@ module Noosfero
15 profile = environment.profiles.find(params[:profile_id]) if params[:profile_id] 16 profile = environment.profiles.find(params[:profile_id]) if params[:profile_id]
16 scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public 17 scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public
17 scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article') 18 scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article')
18 - scope = scope.where(:parent_id => params[:parent_id]) if params[:parent_id].present? 19 + scope = scope.where(make_conditions_with_parameter(params))
19 scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present? 20 scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present?
  21 + scope = scope.where('articles.children_count > 0') if params[:has_children].present?
20 query = params[:query] || "" 22 query = params[:query] || ""
21 order = "more_recent" 23 order = "more_recent"
22 24
23 options = {:filter => order, :template_id => params[:template_id]} 25 options = {:filter => order, :template_id => params[:template_id]}
24 26
25 - paginate_options = params.select{|k,v| [:page, :per_page].include?(k.to_sym)}.symbolize_keys  
26 - paginate_options.each_pair{|k,v| v=v.to_i}  
27 - paginate_options[:page]=1 if !paginate_options.keys.include?(:page)  
28 -  
29 - search_result = find_by_contents(asset, context, scope, query, paginate_options, options) 27 + search_result = find_by_contents(asset, context, scope, query, {:page => 1}, options)
30 28
31 articles = search_result[:results] 29 articles = search_result[:results]
32 30
33 - result = present_articles(articles)  
34 -  
35 - result 31 + present_articles(articles)
36 end 32 end
37 end 33 end
38 end 34 end
lib/noosfero/api/v1/tasks.rb
@@ -20,13 +20,13 @@ module Noosfero @@ -20,13 +20,13 @@ module Noosfero
20 get do 20 get do
21 tasks = select_filtered_collection_of(environment, 'tasks', params) 21 tasks = select_filtered_collection_of(environment, 'tasks', params)
22 tasks = tasks.select {|t| current_person.has_permission?(t.permission, environment)} 22 tasks = tasks.select {|t| current_person.has_permission?(t.permission, environment)}
23 - present tasks, :with => Entities::Task, :fields => params[:fields] 23 + present_partial tasks, :with => Entities::Task
24 end 24 end
25 25
26 desc "Return the task id" 26 desc "Return the task id"
27 get ':id' do 27 get ':id' do
28 task = find_task(environment, params[:id]) 28 task = find_task(environment, params[:id])
29 - present task, :with => Entities::Task, :fields => params[:fields] 29 + present_partial task, :with => Entities::Task
30 end 30 end
31 end 31 end
32 32
test/unit/api/activities_test.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class ActivitiesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + login_api
  7 + end
  8 +
  9 + should 'get activity from profile' do
  10 + person = fast_create(Person)
  11 + organization = fast_create(Organization)
  12 + assert_difference 'organization.activities_count' do
  13 + ActionTracker::Record.create! :verb => :leave_scrap, :user => person, :target => organization
  14 + organization.reload
  15 + end
  16 + get "/api/v1/profiles/#{organization.id}/activities?#{params.to_query}"
  17 + json = JSON.parse(last_response.body)
  18 + assert 1, json["activities"].count
  19 + assert_equal organization.activities.map(&:activity).first.id, json["activities"].first["id"]
  20 + end
  21 +
  22 +end
test/unit/api/articles_test.rb
@@ -13,6 +13,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -13,6 +13,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase
13 assert_includes json["articles"].map { |a| a["id"] }, article.id 13 assert_includes json["articles"].map { |a| a["id"] }, article.id
14 end 14 end
15 15
  16 + should 'get profile homepage' do
  17 + article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  18 + person.home_page=article
  19 + person.save!
  20 +
  21 + get "/api/v1/profiles/#{person.id}/home_page?#{params.to_query}"
  22 + json = JSON.parse(last_response.body)
  23 + assert_equal article.id, json["article"]["id"]
  24 + end
  25 +
16 should 'not list forbidden article when listing articles' do 26 should 'not list forbidden article when listing articles' do
17 person = fast_create(Person) 27 person = fast_create(Person)
18 article = fast_create(Article, :profile_id => person.id, :name => "Some thing", :published => false) 28 article = fast_create(Article, :profile_id => person.id, :name => "Some thing", :published => false)
@@ -74,6 +84,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -74,6 +84,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase
74 assert_equal 1, json['total_followers'] 84 assert_equal 1, json['total_followers']
75 end 85 end
76 86
  87 + should 'list articles followed by me' do
  88 + article1 = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  89 + fast_create(Article, :profile_id => user.person.id, :name => "Some other thing")
  90 + article1.person_followers << @person
  91 + get "/api/v1/articles/followed_by_me?#{params.to_query}"
  92 + json = JSON.parse(last_response.body)
  93 + assert_equal [article1.id], json['articles'].map { |a| a['id'] }
  94 + end
  95 +
  96 +
77 should 'list article children' do 97 should 'list article children' do
78 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing") 98 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
79 child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing") 99 child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing")
@@ -148,6 +168,33 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -148,6 +168,33 @@ class ArticlesTest &lt; ActiveSupport::TestCase
148 end 168 end
149 end 169 end
150 170
  171 + should 'not perform a vote twice in same article' do
  172 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  173 + @params[:value] = 1
  174 + ## Perform a vote twice in API should compute only one vote
  175 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  176 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  177 +
  178 + total = article.votes_total
  179 +
  180 + assert_equal 1, total
  181 + end
  182 +
  183 + should 'not perform a vote in favor and against a proposal' do
  184 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  185 + @params[:value] = 1
  186 + ## Perform a vote in favor a proposal
  187 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  188 + json = JSON.parse(last_response.body)
  189 + assert_equal 201, last_response.status
  190 + ## Perform a vote against a proposal
  191 + @params[:value] = -1
  192 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  193 + json = JSON.parse(last_response.body)
  194 + ## The api should not allow to save this vote
  195 + assert_equal 400, last_response.status
  196 + end
  197 +
151 should "update body of article created by me" do 198 should "update body of article created by me" do
152 new_value = "Another body" 199 new_value = "Another body"
153 params[:article] = {:body => new_value} 200 params[:article] = {:body => new_value}
@@ -606,4 +653,15 @@ class ArticlesTest &lt; ActiveSupport::TestCase @@ -606,4 +653,15 @@ class ArticlesTest &lt; ActiveSupport::TestCase
606 assert_equal json['articles'].count, 2 653 assert_equal json['articles'].count, 2
607 end 654 end
608 655
  656 + ARTICLE_ATTRIBUTES = %w(votes_count comments_count)
  657 +
  658 + ARTICLE_ATTRIBUTES.map do |attribute|
  659 +
  660 + define_method "test_should_expose_#{attribute}_attribute_in_article_enpoints" do
  661 + article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  662 + get "/api/v1/articles/#{article.id}?#{params.to_query}"
  663 + json = JSON.parse(last_response.body)
  664 + assert_not_nil json['article'][attribute]
  665 + end
  666 + end
609 end 667 end
test/unit/api/boxes_test.rb 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class BoxesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + login_api
  7 + end
  8 +
  9 + kinds= %w[Profile Community Person Enterprise]
  10 + kinds.each do |kind|
  11 + should "get_boxes_from_#{kind.downcase.pluralize}" do
  12 + profile_obj = fast_create(kind.constantize)
  13 + box = fast_create(Box, :owner_id => profile_obj.id, :owner_type => "Profile")
  14 + get "/api/v1/#{kind.downcase.pluralize}/#{profile_obj.id}/boxes?#{params.to_query}"
  15 + json = JSON.parse(last_response.body)
  16 + assert_equal box.id, json["boxes"].first["id"]
  17 + end
  18 + end
  19 +
  20 +end
test/unit/api/helpers_test.rb
@@ -25,6 +25,15 @@ class APIHelpersTest &lt; ActiveSupport::TestCase @@ -25,6 +25,15 @@ class APIHelpersTest &lt; ActiveSupport::TestCase
25 assert_equal user, current_user 25 assert_equal user, current_user
26 end 26 end
27 27
  28 + should 'get the current user even with expired token' do
  29 + user = create_user('someuser')
  30 + user.generate_private_token!
  31 + user.private_token_generated_at = DateTime.now.prev_year
  32 + user.save
  33 + self.params = {:private_token => user.private_token}
  34 + assert_equal user, current_user
  35 + end
  36 +
28 should 'get the person of current user' do 37 should 'get the person of current user' do
29 user = create_user('someuser') 38 user = create_user('someuser')
30 user.generate_private_token! 39 user.generate_private_token!
@@ -192,6 +201,26 @@ class APIHelpersTest &lt; ActiveSupport::TestCase @@ -192,6 +201,26 @@ class APIHelpersTest &lt; ActiveSupport::TestCase
192 filter_disabled_plugins_endpoints 201 filter_disabled_plugins_endpoints
193 end 202 end
194 203
  204 + should 'not touch in options when no fields parameter is passed' do
  205 + model = mock
  206 + expects(:present).with(model, {})
  207 + present_partial(model, {})
  208 + end
  209 +
  210 + should 'fallback to array when fields parameter is not a json when calling present partial' do
  211 + model = mock
  212 + params[:fields] = 'name'
  213 + expects(:present).with(model, {:only => ['name']})
  214 + present_partial(model, {})
  215 + end
  216 +
  217 + should 'accept json as fields parameter when calling present partial' do
  218 + model = mock
  219 + params[:fields] = {only: [:name, {user: [:login]}]}.to_json
  220 + expects(:present).with(model, {:only => ['name', {'user' => ['login']}]})
  221 + present_partial(model, {})
  222 + end
  223 +
195 protected 224 protected
196 225
197 def error!(info, status) 226 def error!(info, status)
test/unit/api/people_test.rb
@@ -15,6 +15,19 @@ class PeopleTest &lt; ActiveSupport::TestCase @@ -15,6 +15,19 @@ class PeopleTest &lt; ActiveSupport::TestCase
15 assert_equivalent [person1.id, person2.id, person.id], json['people'].map {|c| c['id']} 15 assert_equivalent [person1.id, person2.id, person.id], json['people'].map {|c| c['id']}
16 end 16 end
17 17
  18 + should 'list all members of a community' do
  19 + person1 = fast_create(Person)
  20 + person2 = fast_create(Person)
  21 + community = fast_create(Community)
  22 + community.add_member(person1)
  23 + community.add_member(person2)
  24 +
  25 + get "/api/v1/profiles/#{community.id}/members?#{params.to_query}"
  26 + json = JSON.parse(last_response.body)
  27 + assert_equal 2, json["people"].count
  28 + assert_equivalent [person1.id,person2.id], json["people"].map{|p| p["id"]}
  29 + end
  30 +
18 should 'not list invisible people' do 31 should 'not list invisible people' do
19 invisible_person = fast_create(Person, :visible => false) 32 invisible_person = fast_create(Person, :visible => false)
20 33
@@ -47,12 +60,34 @@ class PeopleTest &lt; ActiveSupport::TestCase @@ -47,12 +60,34 @@ class PeopleTest &lt; ActiveSupport::TestCase
47 assert_equal some_person.id, json['person']['id'] 60 assert_equal some_person.id, json['person']['id']
48 end 61 end
49 62
  63 + should 'people endpoint filter by fields parameter' do
  64 + get "/api/v1/people?#{params.to_query}&fields=name"
  65 + json = JSON.parse(last_response.body)
  66 + expected = {'people' => [{'name' => person.name}]}
  67 + assert_equal expected, json
  68 + end
  69 +
  70 + should 'people endpoint filter by fields parameter with hierarchy' do
  71 + fields = {only: [:name, {user: [:login]}]}.to_json
  72 + get "/api/v1/people?#{params.to_query}&fields=#{fields}"
  73 + json = JSON.parse(last_response.body)
  74 + expected = {'people' => [{'name' => person.name, 'user' => {'login' => 'testapi'}}]}
  75 + assert_equal expected, json
  76 + end
  77 +
50 should 'get logged person' do 78 should 'get logged person' do
51 get "/api/v1/people/me?#{params.to_query}" 79 get "/api/v1/people/me?#{params.to_query}"
52 json = JSON.parse(last_response.body) 80 json = JSON.parse(last_response.body)
53 assert_equal person.id, json['person']['id'] 81 assert_equal person.id, json['person']['id']
54 end 82 end
55 83
  84 + should 'me endpoint filter by fields parameter' do
  85 + get "/api/v1/people/me?#{params.to_query}&fields=name"
  86 + json = JSON.parse(last_response.body)
  87 + expected = {'person' => {'name' => person.name}}
  88 + assert_equal expected, json
  89 + end
  90 +
56 should 'not get invisible person' do 91 should 'not get invisible person' do
57 person = fast_create(Person, :visible => false) 92 person = fast_create(Person, :visible => false)
58 93
@@ -205,4 +240,19 @@ class PeopleTest &lt; ActiveSupport::TestCase @@ -205,4 +240,19 @@ class PeopleTest &lt; ActiveSupport::TestCase
205 assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog'] 240 assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
206 end 241 end
207 242
  243 + PERSON_ATTRIBUTES = %w(vote_count comments_count articles_count)
  244 +
  245 + PERSON_ATTRIBUTES.map do |attribute|
  246 + define_method "test_should_not_expose_#{attribute}_attribute_in_person_enpoint_if_field_parameter_does_not_contain_the_attribute" do
  247 + get "/api/v1/people/me?#{params.to_query}&fields=name"
  248 + json = JSON.parse(last_response.body)
  249 + assert_nil json['person'][attribute]
  250 + end
  251 +
  252 + define_method "test_should_expose_#{attribute}_attribute_in_person_enpoints_if_field_parameter_is_passed" do
  253 + get "/api/v1/people/me?#{params.to_query}&fields=#{attribute}"
  254 + json = JSON.parse(last_response.body)
  255 + assert_not_nil json['person'][attribute]
  256 + end
  257 + end
208 end 258 end
test/unit/api/profiles_test.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class ProfilesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + Profile.delete_all
  7 + login_api
  8 + end
  9 +
  10 + should 'list all profiles' do
  11 + person1 = fast_create(Person)
  12 + person2 = fast_create(Person)
  13 + community = fast_create(Community)
  14 + get "/api/v1/profiles?#{params.to_query}"
  15 + json = JSON.parse(last_response.body)
  16 + assert_equivalent [person.id, person1.id, person2.id, community.id], json.map {|p| p['id']}
  17 + end
  18 +
  19 + should 'get person from profile id' do
  20 + some_person = fast_create(Person)
  21 + get "/api/v1/profiles/#{some_person.id}?#{params.to_query}"
  22 + json = JSON.parse(last_response.body)
  23 + assert_equal some_person.id, json['id']
  24 + end
  25 +
  26 + should 'get community from profile id' do
  27 + community = fast_create(Community)
  28 + get "/api/v1/profiles/#{community.id}?#{params.to_query}"
  29 + json = JSON.parse(last_response.body)
  30 + assert_equal community.id, json['id']
  31 + end
  32 +end
test/unit/api/search_test.rb
@@ -23,6 +23,16 @@ class SearchTest &lt; ActiveSupport::TestCase @@ -23,6 +23,16 @@ class SearchTest &lt; ActiveSupport::TestCase
23 assert_not_empty json['articles'] 23 assert_not_empty json['articles']
24 end 24 end
25 25
  26 + should 'list only articles that has children' do
  27 + article = fast_create(Article, :profile_id => person.id)
  28 + parent = create(Article, :profile_id => person.id, :name => 'parent article')
  29 + child = create(Article, :profile_id => person.id, :parent_id => parent.id, :name => 'child article')
  30 +
  31 + get "/api/v1/search/article?has_children=true"
  32 + json = JSON.parse(last_response.body)
  33 + assert_equal parent.id, json['articles'].first['id']
  34 + end
  35 +
26 should 'invalid search string articles' do 36 should 'invalid search string articles' do
27 fast_create(Article, :profile_id => person.id, :name => 'some article') 37 fast_create(Article, :profile_id => person.id, :name => 'some article')
28 get "/api/v1/search/article?query=test" 38 get "/api/v1/search/article?query=test"
test/unit/api/session_test.rb
@@ -182,4 +182,14 @@ class SessionTest &lt; ActiveSupport::TestCase @@ -182,4 +182,14 @@ class SessionTest &lt; ActiveSupport::TestCase
182 assert_equal 404, last_response.status 182 assert_equal 404, last_response.status
183 end 183 end
184 184
  185 + should 'not return private token when the registered user is inactive' do
  186 + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com" }
  187 + post "/api/v1/register?#{params.to_query}"
  188 + assert_equal 201, last_response.status
  189 + json = JSON.parse(last_response.body)
  190 + assert !User['newuserapi'].activated?
  191 + assert !json['user']['activated']
  192 + assert !json['user']['private_token'].present?
  193 + end
  194 +
185 end 195 end