Commit 100df3575f11f2c551eb1e950440e3f648f19b98
1 parent
ea20a3dc
Exists in
web_steps_improvements
and in
8 other branches
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>
Showing
23 changed files
with
445 additions
and
61 deletions
Show diff stats
Gemfile
| ... | ... | @@ -34,10 +34,11 @@ gem 'slim' |
| 34 | 34 | |
| 35 | 35 | # API dependencies |
| 36 | 36 | gem 'grape', '~> 0.12' |
| 37 | -gem 'grape-entity' | |
| 37 | +gem 'grape-entity', '0.4.8' | |
| 38 | 38 | gem 'grape_logging' |
| 39 | 39 | gem 'rack-cors' |
| 40 | 40 | gem 'rack-contrib' |
| 41 | +gem 'api-pagination', '~> 4.1.1' | |
| 41 | 42 | |
| 42 | 43 | # asset pipeline |
| 43 | 44 | gem 'uglifier', '>= 1.0.3' | ... | ... |
app/models/profile.rb
| ... | ... | @@ -388,6 +388,10 @@ class Profile < ActiveRecord::Base |
| 388 | 388 | !profiles.exists? |
| 389 | 389 | end |
| 390 | 390 | |
| 391 | + def self.visible_for_person(person) | |
| 392 | + self.all | |
| 393 | + end | |
| 394 | + | |
| 391 | 395 | validates_presence_of :identifier, :name |
| 392 | 396 | validates_length_of :nickname, :maximum => 16, :allow_nil => true |
| 393 | 397 | validate :valid_template | ... | ... |
lib/noosfero/api/api.rb
lib/noosfero/api/entities.rb
| ... | ... | @@ -79,6 +79,18 @@ module Noosfero |
| 79 | 79 | expose :parent_id |
| 80 | 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 | 94 | class Profile < Entity |
| 83 | 95 | expose :identifier, :name, :id |
| 84 | 96 | expose :created_at, :format_with => :timestamp |
| ... | ... | @@ -99,6 +111,7 @@ module Noosfero |
| 99 | 111 | end |
| 100 | 112 | expose :image, :using => Image |
| 101 | 113 | expose :region, :using => Region |
| 114 | + expose :type | |
| 102 | 115 | end |
| 103 | 116 | |
| 104 | 117 | class UserBasic < Entity |
| ... | ... | @@ -109,6 +122,16 @@ module Noosfero |
| 109 | 122 | class Person < Profile |
| 110 | 123 | root 'people', 'person' |
| 111 | 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 | 135 | end |
| 113 | 136 | |
| 114 | 137 | class Enterprise < Profile |
| ... | ... | @@ -149,6 +172,8 @@ module Noosfero |
| 149 | 172 | expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"} |
| 150 | 173 | expose :path |
| 151 | 174 | expose :followers_count |
| 175 | + expose :votes_count | |
| 176 | + expose :comments_count | |
| 152 | 177 | end |
| 153 | 178 | |
| 154 | 179 | class Article < ArticleBase |
| ... | ... | @@ -189,7 +214,7 @@ module Noosfero |
| 189 | 214 | |
| 190 | 215 | class UserLogin < User |
| 191 | 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 | 218 | end |
| 194 | 219 | |
| 195 | 220 | class Task < Entity |
| ... | ... | @@ -207,7 +232,15 @@ module Noosfero |
| 207 | 232 | expose :name |
| 208 | 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 | 244 | end |
| 212 | 245 | end |
| 213 | 246 | end | ... | ... |
lib/noosfero/api/entity.rb
| ... | ... | @@ -22,18 +22,4 @@ class Noosfero::API::Entity < Grape::Entity |
| 22 | 22 | end |
| 23 | 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 | 25 | end | ... | ... |
lib/noosfero/api/helpers.rb
| ... | ... | @@ -5,7 +5,7 @@ require_relative '../../find_by_contents' |
| 5 | 5 | module API |
| 6 | 6 | module APIHelpers |
| 7 | 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 | 10 | include SanitizeParams |
| 11 | 11 | include Noosfero::Plugin::HotSpot |
| ... | ... | @@ -38,6 +38,20 @@ require_relative '../../find_by_contents' |
| 38 | 38 | @environment |
| 39 | 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 | 55 | include FindByContents |
| 42 | 56 | |
| 43 | 57 | #################################################################### |
| ... | ... | @@ -87,7 +101,7 @@ require_relative '../../find_by_contents' |
| 87 | 101 | def post_article(asset, params) |
| 88 | 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 | 105 | return forbidden! unless ARTICLE_TYPES.include?(klass_type) |
| 92 | 106 | |
| 93 | 107 | article = klass_type.constantize.new(params[:article]) |
| ... | ... | @@ -98,12 +112,12 @@ require_relative '../../find_by_contents' |
| 98 | 112 | if !article.save |
| 99 | 113 | render_api_errors!(article.errors.full_messages) |
| 100 | 114 | end |
| 101 | - present article, :with => Entities::Article, :fields => params[:fields] | |
| 115 | + present_partial article, :with => Entities::Article | |
| 102 | 116 | end |
| 103 | 117 | |
| 104 | 118 | def present_article(asset) |
| 105 | 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 | 121 | end |
| 108 | 122 | |
| 109 | 123 | def present_articles_for_asset(asset, method = 'articles') |
| ... | ... | @@ -112,7 +126,7 @@ require_relative '../../find_by_contents' |
| 112 | 126 | end |
| 113 | 127 | |
| 114 | 128 | def present_articles(articles) |
| 115 | - present articles, :with => Entities::Article, :fields => params[:fields] | |
| 129 | + present_partial paginate(articles), :with => Entities::Article | |
| 116 | 130 | end |
| 117 | 131 | |
| 118 | 132 | def find_articles(asset, method = 'articles') |
| ... | ... | @@ -142,19 +156,19 @@ require_relative '../../find_by_contents' |
| 142 | 156 | if !task.save |
| 143 | 157 | render_api_errors!(task.errors.full_messages) |
| 144 | 158 | end |
| 145 | - present task, :with => Entities::Task, :fields => params[:fields] | |
| 159 | + present_partial task, :with => Entities::Task | |
| 146 | 160 | end |
| 147 | 161 | |
| 148 | 162 | def present_task(asset) |
| 149 | 163 | task = find_task(asset, params[:id]) |
| 150 | - present task, :with => Entities::Task, :fields => params[:fields] | |
| 164 | + present_partial task, :with => Entities::Task | |
| 151 | 165 | end |
| 152 | 166 | |
| 153 | 167 | def present_tasks(asset) |
| 154 | 168 | tasks = select_filtered_collection_of(asset, 'tasks', params) |
| 155 | 169 | tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)} |
| 156 | 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 | 172 | end |
| 159 | 173 | |
| 160 | 174 | def make_conditions_with_parameter(params = {}) |
| ... | ... | @@ -194,15 +208,6 @@ require_relative '../../find_by_contents' |
| 194 | 208 | return order |
| 195 | 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 | 211 | def make_timestamp_with_parameters_and_method(params, method) |
| 207 | 212 | timestamp = nil |
| 208 | 213 | if params[:timestamp] |
| ... | ... | @@ -236,17 +241,17 @@ require_relative '../../find_by_contents' |
| 236 | 241 | def select_filtered_collection_of(object, method, params) |
| 237 | 242 | conditions = make_conditions_with_parameter(params) |
| 238 | 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 | 244 | timestamp = make_timestamp_with_parameters_and_method(params, method) |
| 242 | 245 | |
| 243 | 246 | objects = object.send(method) |
| 244 | 247 | objects = by_reference(objects, params) |
| 245 | 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 | 255 | end |
| 251 | 256 | |
| 252 | 257 | def authenticate! | ... | ... |
lib/noosfero/api/session.rb
| ... | ... | @@ -24,6 +24,14 @@ module Noosfero |
| 24 | 24 | present user, :with => Entities::UserLogin, :current_person => current_person |
| 25 | 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 | 35 | # Create user. |
| 28 | 36 | # |
| 29 | 37 | # Parameters: | ... | ... |
| ... | ... | @@ -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 | 5 | |
| 6 | 6 | ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} |
| 7 | 7 | |
| 8 | + MAX_PER_PAGE = 50 | |
| 9 | + | |
| 8 | 10 | resource :articles do |
| 9 | 11 | |
| 12 | + paginate max_per_page: MAX_PER_PAGE | |
| 10 | 13 | # Collect articles |
| 11 | 14 | # |
| 12 | 15 | # Parameters: |
| ... | ... | @@ -49,7 +52,7 @@ module Noosfero |
| 49 | 52 | article = environment.articles.find(params[:id]) |
| 50 | 53 | return forbidden! unless article.allow_edit?(current_person) |
| 51 | 54 | article.update_attributes!(params[:article]) |
| 52 | - present article, :with => Entities::Article, :fields => params[:fields] | |
| 55 | + present_partial article, :with => Entities::Article | |
| 53 | 56 | end |
| 54 | 57 | |
| 55 | 58 | desc 'Report a abuse and/or violent content in a article by id' do |
| ... | ... | @@ -93,7 +96,7 @@ module Noosfero |
| 93 | 96 | end |
| 94 | 97 | #FIXME refactor this method |
| 95 | 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 | 100 | end |
| 98 | 101 | |
| 99 | 102 | desc 'Perform a vote on a article by id' do |
| ... | ... | @@ -108,8 +111,12 @@ module Noosfero |
| 108 | 111 | # FIXME verify allowed values |
| 109 | 112 | render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value) |
| 110 | 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 | 120 | end |
| 114 | 121 | |
| 115 | 122 | desc "Returns the total followers for the article" do |
| ... | ... | @@ -123,6 +130,11 @@ module Noosfero |
| 123 | 130 | {:total_followers => total} |
| 124 | 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 | 138 | desc "Add a follower for the article" do |
| 127 | 139 | detail 'Add the current user identified by private token, like a follower of a article' |
| 128 | 140 | params Noosfero::API::Entities::UserLogin.documentation |
| ... | ... | @@ -150,6 +162,7 @@ module Noosfero |
| 150 | 162 | named 'ArticleChildren' |
| 151 | 163 | end |
| 152 | 164 | |
| 165 | + paginate per_page: MAX_PER_PAGE, max_per_page: MAX_PER_PAGE | |
| 153 | 166 | get ':id/children' do |
| 154 | 167 | article = find_article(environment.articles, params[:id]) |
| 155 | 168 | |
| ... | ... | @@ -177,7 +190,7 @@ module Noosfero |
| 177 | 190 | article = find_article(environment.articles, params[:id]) |
| 178 | 191 | child = find_article(article.children, params[:child_id]) |
| 179 | 192 | child.hit |
| 180 | - present child, :with => Entities::Article, :fields => params[:fields] | |
| 193 | + present_partial child, :with => Entities::Article | |
| 181 | 194 | end |
| 182 | 195 | |
| 183 | 196 | desc 'Suggest a article to another profile' do |
| ... | ... | @@ -200,7 +213,7 @@ module Noosfero |
| 200 | 213 | unless suggest_article.save |
| 201 | 214 | render_api_errors!(suggest_article.article_object.errors.full_messages) |
| 202 | 215 | end |
| 203 | - present suggest_article, :with => Entities::Task, :fields => params[:fields] | |
| 216 | + present_partial suggest_article, :with => Entities::Task | |
| 204 | 217 | end |
| 205 | 218 | |
| 206 | 219 | # Example Request: |
| ... | ... | @@ -231,12 +244,21 @@ module Noosfero |
| 231 | 244 | if !article.save |
| 232 | 245 | render_api_errors!(article.errors.full_messages) |
| 233 | 246 | end |
| 234 | - present article, :with => Entities::Article, :fields => params[:fields] | |
| 247 | + present_partial article, :with => Entities::Article | |
| 235 | 248 | end |
| 236 | 249 | |
| 237 | 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 | 262 | kinds.each do |kind| |
| 241 | 263 | resource kind.pluralize.to_sym do |
| 242 | 264 | segment "/:#{kind}_id" do |
| ... | ... | @@ -258,7 +280,7 @@ module Noosfero |
| 258 | 280 | article = forbidden! |
| 259 | 281 | end |
| 260 | 282 | |
| 261 | - present article, :with => Entities::Article, :fields => params[:fields] | |
| 283 | + present_partial article, :with => Entities::Article | |
| 262 | 284 | else |
| 263 | 285 | |
| 264 | 286 | present_articles_for_asset(profile) | ... | ... |
| ... | ... | @@ -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 | 31 | post ":id/comments" do |
| 32 | 32 | article = find_article(environment.articles, params[:id]) |
| 33 | 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 | 40 | end |
| 36 | 41 | end |
| 37 | 42 | ... | ... |
lib/noosfero/api/v1/people.rb
| ... | ... | @@ -4,9 +4,12 @@ module Noosfero |
| 4 | 4 | class People < Grape::API |
| 5 | 5 | before { authenticate! } |
| 6 | 6 | |
| 7 | + MAX_PER_PAGE = 50 | |
| 8 | + | |
| 7 | 9 | desc 'API Root' |
| 8 | 10 | |
| 9 | 11 | resource :people do |
| 12 | + paginate max_per_page: MAX_PER_PAGE | |
| 10 | 13 | |
| 11 | 14 | # -- A note about privacy -- |
| 12 | 15 | # We wold find people by location, but we must test if the related |
| ... | ... | @@ -33,12 +36,12 @@ module Noosfero |
| 33 | 36 | get do |
| 34 | 37 | people = select_filtered_collection_of(environment, 'people', params) |
| 35 | 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 | 40 | end |
| 38 | 41 | |
| 39 | 42 | desc "Return the logged user information" |
| 40 | 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 | 45 | end |
| 43 | 46 | |
| 44 | 47 | desc "Return the person information" |
| ... | ... | @@ -105,6 +108,19 @@ module Noosfero |
| 105 | 108 | present output |
| 106 | 109 | end |
| 107 | 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 | 124 | end |
| 109 | 125 | end |
| 110 | 126 | end | ... | ... |
| ... | ... | @@ -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 | 5 | |
| 6 | 6 | resource :search do |
| 7 | 7 | resource :article do |
| 8 | + paginate max_per_page: 200 | |
| 8 | 9 | get do |
| 9 | 10 | # Security checks |
| 10 | 11 | sanitize_params_hash(params) |
| ... | ... | @@ -15,24 +16,19 @@ module Noosfero |
| 15 | 16 | profile = environment.profiles.find(params[:profile_id]) if params[:profile_id] |
| 16 | 17 | scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public |
| 17 | 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 | 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 | 22 | query = params[:query] || "" |
| 21 | 23 | order = "more_recent" |
| 22 | 24 | |
| 23 | 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 | 29 | articles = search_result[:results] |
| 32 | 30 | |
| 33 | - result = present_articles(articles) | |
| 34 | - | |
| 35 | - result | |
| 31 | + present_articles(articles) | |
| 36 | 32 | end |
| 37 | 33 | end |
| 38 | 34 | end | ... | ... |
lib/noosfero/api/v1/tasks.rb
| ... | ... | @@ -20,13 +20,13 @@ module Noosfero |
| 20 | 20 | get do |
| 21 | 21 | tasks = select_filtered_collection_of(environment, 'tasks', params) |
| 22 | 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 | 24 | end |
| 25 | 25 | |
| 26 | 26 | desc "Return the task id" |
| 27 | 27 | get ':id' do |
| 28 | 28 | task = find_task(environment, params[:id]) |
| 29 | - present task, :with => Entities::Task, :fields => params[:fields] | |
| 29 | + present_partial task, :with => Entities::Task | |
| 30 | 30 | end |
| 31 | 31 | end |
| 32 | 32 | ... | ... |
| ... | ... | @@ -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 < ActiveSupport::TestCase |
| 13 | 13 | assert_includes json["articles"].map { |a| a["id"] }, article.id |
| 14 | 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 | 26 | should 'not list forbidden article when listing articles' do |
| 17 | 27 | person = fast_create(Person) |
| 18 | 28 | article = fast_create(Article, :profile_id => person.id, :name => "Some thing", :published => false) |
| ... | ... | @@ -74,6 +84,16 @@ class ArticlesTest < ActiveSupport::TestCase |
| 74 | 84 | assert_equal 1, json['total_followers'] |
| 75 | 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 | 97 | should 'list article children' do |
| 78 | 98 | article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing") |
| 79 | 99 | child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing") |
| ... | ... | @@ -148,6 +168,33 @@ class ArticlesTest < ActiveSupport::TestCase |
| 148 | 168 | end |
| 149 | 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 | 198 | should "update body of article created by me" do |
| 152 | 199 | new_value = "Another body" |
| 153 | 200 | params[:article] = {:body => new_value} |
| ... | ... | @@ -606,4 +653,15 @@ class ArticlesTest < ActiveSupport::TestCase |
| 606 | 653 | assert_equal json['articles'].count, 2 |
| 607 | 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 | 667 | end | ... | ... |
| ... | ... | @@ -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 < ActiveSupport::TestCase |
| 25 | 25 | assert_equal user, current_user |
| 26 | 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 | 37 | should 'get the person of current user' do |
| 29 | 38 | user = create_user('someuser') |
| 30 | 39 | user.generate_private_token! |
| ... | ... | @@ -192,6 +201,26 @@ class APIHelpersTest < ActiveSupport::TestCase |
| 192 | 201 | filter_disabled_plugins_endpoints |
| 193 | 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 | 224 | protected |
| 196 | 225 | |
| 197 | 226 | def error!(info, status) | ... | ... |
test/unit/api/people_test.rb
| ... | ... | @@ -15,6 +15,19 @@ class PeopleTest < ActiveSupport::TestCase |
| 15 | 15 | assert_equivalent [person1.id, person2.id, person.id], json['people'].map {|c| c['id']} |
| 16 | 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 | 31 | should 'not list invisible people' do |
| 19 | 32 | invisible_person = fast_create(Person, :visible => false) |
| 20 | 33 | |
| ... | ... | @@ -47,12 +60,34 @@ class PeopleTest < ActiveSupport::TestCase |
| 47 | 60 | assert_equal some_person.id, json['person']['id'] |
| 48 | 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 | 78 | should 'get logged person' do |
| 51 | 79 | get "/api/v1/people/me?#{params.to_query}" |
| 52 | 80 | json = JSON.parse(last_response.body) |
| 53 | 81 | assert_equal person.id, json['person']['id'] |
| 54 | 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 | 91 | should 'not get invisible person' do |
| 57 | 92 | person = fast_create(Person, :visible => false) |
| 58 | 93 | |
| ... | ... | @@ -205,4 +240,19 @@ class PeopleTest < ActiveSupport::TestCase |
| 205 | 240 | assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog'] |
| 206 | 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 | 258 | end | ... | ... |
| ... | ... | @@ -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 < ActiveSupport::TestCase |
| 23 | 23 | assert_not_empty json['articles'] |
| 24 | 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 | 36 | should 'invalid search string articles' do |
| 27 | 37 | fast_create(Article, :profile_id => person.id, :name => 'some article') |
| 28 | 38 | get "/api/v1/search/article?query=test" | ... | ... |
test/unit/api/session_test.rb
| ... | ... | @@ -182,4 +182,14 @@ class SessionTest < ActiveSupport::TestCase |
| 182 | 182 | assert_equal 404, last_response.status |
| 183 | 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 | 195 | end | ... | ... |