Commit 74184b5cfa4c2605751259d3fcd3dbbb77e52bf1
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
fix entities api conflit
Showing
20 changed files
with
425 additions
and
15 deletions
Show diff stats
Gemfile
| @@ -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' |
Gemfile.lock
| @@ -103,12 +103,16 @@ GEM | @@ -103,12 +103,16 @@ GEM | ||
| 103 | grape-entity (0.4.5) | 103 | grape-entity (0.4.5) |
| 104 | activesupport | 104 | activesupport |
| 105 | multi_json (>= 1.3.2) | 105 | multi_json (>= 1.3.2) |
| 106 | + grape-swagger (0.10.2) | ||
| 107 | + grape (>= 0.8.0) | ||
| 108 | + grape-entity | ||
| 106 | hashie (2.1.2) | 109 | hashie (2.1.2) |
| 107 | hike (1.2.3) | 110 | hike (1.2.3) |
| 108 | i18n (0.7.0) | 111 | i18n (0.7.0) |
| 109 | ice_nine (0.11.1) | 112 | ice_nine (0.11.1) |
| 110 | journey (1.0.4) | 113 | journey (1.0.4) |
| 111 | json (1.8.3) | 114 | json (1.8.3) |
| 115 | + kramdown (1.8.0) | ||
| 112 | libv8 (3.16.14.11) | 116 | libv8 (3.16.14.11) |
| 113 | liquid (3.0.6) | 117 | liquid (3.0.6) |
| 114 | locale (2.0.9) | 118 | locale (2.0.9) |
| @@ -202,6 +206,7 @@ GEM | @@ -202,6 +206,7 @@ GEM | ||
| 202 | multi_json (~> 1.0) | 206 | multi_json (~> 1.0) |
| 203 | rack (~> 1.0) | 207 | rack (~> 1.0) |
| 204 | tilt (~> 1.1, != 1.3.0) | 208 | tilt (~> 1.1, != 1.3.0) |
| 209 | + swagger-ui_rails (0.1.7) | ||
| 205 | term-ansicolor (1.3.2) | 210 | term-ansicolor (1.3.2) |
| 206 | tins (~> 1.0) | 211 | tins (~> 1.0) |
| 207 | therubyracer (0.12.2) | 212 | therubyracer (0.12.2) |
| @@ -254,7 +259,9 @@ DEPENDENCIES | @@ -254,7 +259,9 @@ DEPENDENCIES | ||
| 254 | gettext (~> 2.2.1) | 259 | gettext (~> 2.2.1) |
| 255 | grape (~> 0.12) | 260 | grape (~> 0.12) |
| 256 | grape-entity | 261 | grape-entity |
| 262 | + grape-swagger | ||
| 257 | grape_logging! | 263 | grape_logging! |
| 264 | + kramdown | ||
| 258 | liquid (~> 3.0.3) | 265 | liquid (~> 3.0.3) |
| 259 | locale (~> 2.0.5) | 266 | locale (~> 2.0.5) |
| 260 | minitest (~> 3.2.0) | 267 | minitest (~> 3.2.0) |
| @@ -274,6 +281,7 @@ DEPENDENCIES | @@ -274,6 +281,7 @@ DEPENDENCIES | ||
| 274 | rubyzip | 281 | rubyzip |
| 275 | sass-rails | 282 | sass-rails |
| 276 | selenium-webdriver (~> 2.39.0) | 283 | selenium-webdriver (~> 2.39.0) |
| 284 | + swagger-ui_rails | ||
| 277 | therubyracer | 285 | therubyracer |
| 278 | thin (~> 1.3.1) | 286 | thin (~> 1.3.1) |
| 279 | uglifier (>= 1.0.3) | 287 | uglifier (>= 1.0.3) |
app/controllers/public/account_controller.rb
| @@ -196,6 +196,11 @@ class AccountController < ApplicationController | @@ -196,6 +196,11 @@ class AccountController < ApplicationController | ||
| 196 | 196 | ||
| 197 | if request.post? | 197 | if request.post? |
| 198 | begin | 198 | begin |
| 199 | + unless verify_recaptcha | ||
| 200 | + @change_password.errors.add(:base, _('Please type the words correctly')) | ||
| 201 | + return false | ||
| 202 | + end | ||
| 203 | + | ||
| 199 | requestors = fetch_requestors(params[:value]) | 204 | requestors = fetch_requestors(params[:value]) |
| 200 | raise ActiveRecord::RecordNotFound if requestors.blank? || params[:value].blank? | 205 | raise ActiveRecord::RecordNotFound if requestors.blank? || params[:value].blank? |
| 201 | 206 |
app/views/account/forgot_password.html.erb
| @@ -5,6 +5,9 @@ | @@ -5,6 +5,9 @@ | ||
| 5 | <%= form_tag({:action => 'forgot_password'}, :method => 'post', :id => 'forgot-password-form') do %> | 5 | <%= form_tag({:action => 'forgot_password'}, :method => 'post', :id => 'forgot-password-form') do %> |
| 6 | <%= labelled_form_field fields_label, text_field_tag(:value) %> | 6 | <%= labelled_form_field fields_label, text_field_tag(:value) %> |
| 7 | 7 | ||
| 8 | + <h3><%= _('Please type the two words below') %></h3> | ||
| 9 | + <%= recaptcha_tags(:display => { :theme => 'clean' }, :ajax => true) %> | ||
| 10 | + | ||
| 8 | <div> | 11 | <div> |
| 9 | <% button_bar do %> | 12 | <% button_bar do %> |
| 10 | <%= submit_button('send', _('Send instructions')) %> | 13 | <%= submit_button('send', _('Send instructions')) %> |
| @@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
| 1 | +<%= render 'swagger_ui/swagger_ui', discovery_url: '/api/v1/api_docs' %> |
| @@ -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
| @@ -51,9 +51,12 @@ module Noosfero | @@ -51,9 +51,12 @@ module Noosfero | ||
| 51 | mount V1::Tasks | 51 | mount V1::Tasks |
| 52 | mount V1::Tags | 52 | mount V1::Tags |
| 53 | mount V1::Environments | 53 | mount V1::Environments |
| 54 | + mount V1::Search | ||
| 54 | 55 | ||
| 55 | mount Session | 56 | mount Session |
| 56 | 57 | ||
| 58 | + add_swagger_documentation api_version: 'v1', mount_path: '/api_docs', markdown: GrapeSwagger::Markdown::KramdownAdapter unless Rails.env.production? | ||
| 59 | + | ||
| 57 | # hook point which allow plugins to add Grape::API extensions to API::API | 60 | # 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) | 61 | #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 } | 62 | @plugins = Noosfero::Plugin.all.map { |p| p.constantize } |
lib/noosfero/api/entities.rb
| @@ -67,7 +67,7 @@ module Noosfero | @@ -67,7 +67,7 @@ module Noosfero | ||
| 67 | 67 | ||
| 68 | class Person < Profile | 68 | class Person < Profile |
| 69 | root 'people', 'person' | 69 | root 'people', 'person' |
| 70 | - expose :user, :using => UserBasic | 70 | + expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' } |
| 71 | expose :orientacao_sexual, :identidade_genero, :transgenero, :etnia | 71 | expose :orientacao_sexual, :identidade_genero, :transgenero, :etnia |
| 72 | end | 72 | end |
| 73 | 73 | ||
| @@ -84,11 +84,11 @@ module Noosfero | @@ -84,11 +84,11 @@ module Noosfero | ||
| 84 | root 'articles', 'article' | 84 | root 'articles', 'article' |
| 85 | expose :id | 85 | expose :id |
| 86 | expose :body | 86 | expose :body |
| 87 | - expose :abstract | 87 | + expose :abstract, documentation: {type: 'String', desc: 'Teaser of the body'} |
| 88 | expose :created_at, :format_with => :timestamp | 88 | expose :created_at, :format_with => :timestamp |
| 89 | expose :title, :documentation => {:type => "String", :desc => "Title of the article"} | 89 | expose :title, :documentation => {:type => "String", :desc => "Title of the article"} |
| 90 | - expose :created_by, :as => :author, :using => Profile | ||
| 91 | - expose :profile, :using => Profile | 90 | + expose :created_by, :as => :author, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile author that create the article'} |
| 91 | + expose :profile, :using => Profile, :documentation => {type: 'Profile', desc: 'The profile associated with the article'} | ||
| 92 | expose :categories, :using => Category | 92 | expose :categories, :using => Category |
| 93 | expose :image, :using => Image | 93 | expose :image, :using => Image |
| 94 | #TODO Apply vote stuff in core and make this test | 94 | #TODO Apply vote stuff in core and make this test |
| @@ -98,7 +98,7 @@ module Noosfero | @@ -98,7 +98,7 @@ module Noosfero | ||
| 98 | expose :position | 98 | expose :position |
| 99 | expose :hits | 99 | expose :hits |
| 100 | expose :start_date | 100 | expose :start_date |
| 101 | - expose :end_date | 101 | + expose :end_date, :documentation => {type: 'DateTime', desc: 'The date of finish of the article'} |
| 102 | expose :tag_list | 102 | expose :tag_list |
| 103 | expose :children_count | 103 | expose :children_count |
| 104 | end | 104 | end |
| @@ -109,6 +109,7 @@ module Noosfero | @@ -109,6 +109,7 @@ module Noosfero | ||
| 109 | expose :children, using: ArticleBase do |article, options| | 109 | expose :children, using: ArticleBase do |article, options| |
| 110 | article.children.limit(Noosfero::API::V1::Articles::MAX_PER_PAGE) | 110 | article.children.limit(Noosfero::API::V1::Articles::MAX_PER_PAGE) |
| 111 | end | 111 | end |
| 112 | + expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"} | ||
| 112 | end | 113 | end |
| 113 | 114 | ||
| 114 | class Comment < Entity | 115 | class Comment < Entity |
| @@ -138,7 +139,7 @@ module Noosfero | @@ -138,7 +139,7 @@ module Noosfero | ||
| 138 | end | 139 | end |
| 139 | 140 | ||
| 140 | class UserLogin < User | 141 | class UserLogin < User |
| 141 | - expose :private_token | 142 | + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'} |
| 142 | end | 143 | end |
| 143 | 144 | ||
| 144 | class Task < Entity | 145 | class Task < Entity |
lib/noosfero/api/helpers.rb
| @@ -10,6 +10,7 @@ require 'grape' | @@ -10,6 +10,7 @@ require 'grape' | ||
| 10 | include SanitizeParams | 10 | include SanitizeParams |
| 11 | include Noosfero::Plugin::HotSpot | 11 | include Noosfero::Plugin::HotSpot |
| 12 | include ForgotPasswordHelper | 12 | include ForgotPasswordHelper |
| 13 | + include SearchTermHelper | ||
| 13 | 14 | ||
| 14 | def set_locale | 15 | def set_locale |
| 15 | I18n.locale = (params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en') | 16 | I18n.locale = (params[:lang] || request.env['HTTP_ACCEPT_LANGUAGE'] || 'en') |
| @@ -39,6 +40,24 @@ require 'grape' | @@ -39,6 +40,24 @@ require 'grape' | ||
| 39 | @environment | 40 | @environment |
| 40 | end | 41 | end |
| 41 | 42 | ||
| 43 | + #################################################################### | ||
| 44 | + #### SEARCH | ||
| 45 | + #################################################################### | ||
| 46 | + def find_by_contents(asset, context, scope, query, paginate_options={:page => 1}, options={}) | ||
| 47 | + scope = scope.with_templates(options[:template_id]) unless options[:template_id].blank? | ||
| 48 | + search = plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) | ||
| 49 | + register_search_term(query, scope.count, search[:results].count, context, asset) | ||
| 50 | + search | ||
| 51 | + end | ||
| 52 | + def paginate_options(page = params[:page]) | ||
| 53 | + page = 1 if multiple_search?(@searches) || params[:display] == 'map' | ||
| 54 | + { :per_page => limit, :page => page } | ||
| 55 | + end | ||
| 56 | + def multiple_search?(searches=nil) | ||
| 57 | + ['index', 'category_index'].include?(params[:action]) || (searches && searches.size > 1) | ||
| 58 | + end | ||
| 59 | + #################################################################### | ||
| 60 | + | ||
| 42 | def logger | 61 | def logger |
| 43 | logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log")) | 62 | logger = Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV'] || 'production'}_api.log")) |
| 44 | logger.formatter = GrapeLogging::Formatters::Default.new | 63 | logger.formatter = GrapeLogging::Formatters::Default.new |
| @@ -99,7 +118,11 @@ require 'grape' | @@ -99,7 +118,11 @@ require 'grape' | ||
| 99 | 118 | ||
| 100 | def present_articles(asset, method = 'articles') | 119 | def present_articles(asset, method = 'articles') |
| 101 | articles = find_articles(asset, method) | 120 | articles = find_articles(asset, method) |
| 102 | - articles = paginate articles | 121 | + present_articles_paginated(articles) |
| 122 | + end | ||
| 123 | + | ||
| 124 | + def present_articles_paginated(articles, per_page=nil) | ||
| 125 | + articles = paginate(articles) | ||
| 103 | present articles, :with => Entities::Article, :fields => params[:fields] | 126 | present articles, :with => Entities::Article, :fields => params[:fields] |
| 104 | end | 127 | end |
| 105 | 128 |
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) |
| @@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
| 1 | +module Noosfero | ||
| 2 | + module API | ||
| 3 | + module V1 | ||
| 4 | + class Search < Grape::API | ||
| 5 | + | ||
| 6 | + resource :search do | ||
| 7 | + resource :article do | ||
| 8 | + paginate per_page: 20, max_per_page: 200 | ||
| 9 | + get do | ||
| 10 | + # Security checks | ||
| 11 | + sanitize_params_hash(params) | ||
| 12 | + # APIHelpers | ||
| 13 | + asset = :articles | ||
| 14 | + context = environment | ||
| 15 | + | ||
| 16 | + profile = environment.profiles.find(params[:profile_id]) if params[:profile_id] | ||
| 17 | + | ||
| 18 | + scope = profile.nil? ? environment.articles.public : profile.articles.public | ||
| 19 | + | ||
| 20 | + scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article') | ||
| 21 | + | ||
| 22 | + category = params[:category] || "" | ||
| 23 | + | ||
| 24 | + query = params[:query] || "" | ||
| 25 | + order = "more_recent" | ||
| 26 | + | ||
| 27 | + options = {:filter => order, :template_id => params[:template_id], :category => category} | ||
| 28 | + | ||
| 29 | + search_result = find_by_contents(asset, context, scope, query, paginate_options, options) | ||
| 30 | + | ||
| 31 | + articles = search_result[:results] | ||
| 32 | + | ||
| 33 | + result = present_articles_paginated(articles) | ||
| 34 | + | ||
| 35 | + result | ||
| 36 | + end | ||
| 37 | + end | ||
| 38 | + end | ||
| 39 | + | ||
| 40 | + end | ||
| 41 | + end | ||
| 42 | + end | ||
| 43 | +end |
plugins/oauth_client/controllers/public/oauth_client_plugin_public_controller.rb
| @@ -36,6 +36,7 @@ class OauthClientPluginPublicController < PublicController | @@ -36,6 +36,7 @@ class OauthClientPluginPublicController < PublicController | ||
| 36 | auth ||= person.oauth_auths.create! profile: person, provider: provider, enabled: true | 36 | auth ||= person.oauth_auths.create! profile: person, provider: provider, enabled: true |
| 37 | if auth.enabled? && provider.enabled? | 37 | if auth.enabled? && provider.enabled? |
| 38 | self.current_user = person.user | 38 | self.current_user = person.user |
| 39 | + self.current_user.generate_private_token! | ||
| 39 | else | 40 | else |
| 40 | session[:notice] = _("Can't login with %s") % provider.name | 41 | session[:notice] = _("Can't login with %s") % provider.name |
| 41 | end | 42 | end |
plugins/oauth_client/db/migrate/20150815173209_add_authorization_data_to_oauth_client_user_provider.rb
| @@ -12,8 +12,11 @@ class AddAuthorizationDataToOauthClientUserProvider < ActiveRecord::Migration | @@ -12,8 +12,11 @@ class AddAuthorizationDataToOauthClientUserProvider < ActiveRecord::Migration | ||
| 12 | 12 | ||
| 13 | add_column :oauth_client_plugin_auths, :profile_id, :integer | 13 | add_column :oauth_client_plugin_auths, :profile_id, :integer |
| 14 | OauthClientPlugin::Auth.find_each batch_size: 50 do |auth| | 14 | OauthClientPlugin::Auth.find_each batch_size: 50 do |auth| |
| 15 | - auth.profile = User.find(auth.user_id).person | ||
| 16 | - auth.save! | 15 | + user = User.find_by_id(auth.user_id) |
| 16 | + if user.present? | ||
| 17 | + auth.profile = user.person | ||
| 18 | + auth.save! | ||
| 19 | + end | ||
| 17 | end | 20 | end |
| 18 | remove_column :oauth_client_plugin_auths, :user_id | 21 | remove_column :oauth_client_plugin_auths, :user_id |
| 19 | 22 |
plugins/oauth_client/test/functional/oauth_client_plugin_public_controller_test.rb
| @@ -31,6 +31,17 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | @@ -31,6 +31,17 @@ class OauthClientPluginPublicControllerTest < ActionController::TestCase | ||
| 31 | assert_equal user.id, session[:user] | 31 | assert_equal user.id, session[:user] |
| 32 | end | 32 | end |
| 33 | 33 | ||
| 34 | + should 'generate private token when login' do | ||
| 35 | + user = create_user | ||
| 36 | + auth.info.stubs(:email).returns(user.email) | ||
| 37 | + auth.info.stubs(:name).returns(user.name) | ||
| 38 | + session[:provider_id] = provider.id | ||
| 39 | + | ||
| 40 | + assert user.reload.private_token.nil? | ||
| 41 | + get :callback | ||
| 42 | + assert user.reload.private_token.present? | ||
| 43 | + end | ||
| 44 | + | ||
| 34 | should 'do not login when the provider is disabled' do | 45 | should 'do not login when the provider is disabled' do |
| 35 | user = create_user | 46 | user = create_user |
| 36 | auth.info.stubs(:email).returns(user.email) | 47 | auth.info.stubs(:email).returns(user.email) |
test/functional/account_controller_test.rb
| @@ -232,6 +232,16 @@ class AccountControllerTest < ActionController::TestCase | @@ -232,6 +232,16 @@ class AccountControllerTest < ActionController::TestCase | ||
| 232 | assert_template 'password_recovery_sent' | 232 | assert_template 'password_recovery_sent' |
| 233 | end | 233 | end |
| 234 | 234 | ||
| 235 | + should 'not respond to forgotten password change if captcha verification fails' do | ||
| 236 | + create_user('test') | ||
| 237 | + @controller.stubs(:verify_recaptcha).returns(false) | ||
| 238 | + post :forgot_password, :value => 'test' | ||
| 239 | + change = assigns(:change_password) | ||
| 240 | + assert change.errors.has_key?(:base) | ||
| 241 | + assert_response :success | ||
| 242 | + assert_tag :tag => 'div', :attributes => { :id => 'errorExplanation', :class => 'errorExplanation' } | ||
| 243 | + end | ||
| 244 | + | ||
| 235 | should 'respond to forgotten password change request with email' do | 245 | should 'respond to forgotten password change request with email' do |
| 236 | change = ChangePassword.new | 246 | change = ChangePassword.new |
| 237 | create_user('test', :email => 'test@localhost.localdomain') | 247 | create_user('test', :email => 'test@localhost.localdomain') |
test/unit/api/articles_test.rb
| @@ -91,6 +91,41 @@ class ArticlesTest < ActiveSupport::TestCase | @@ -91,6 +91,41 @@ class ArticlesTest < 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| |
| @@ -180,6 +215,29 @@ class ArticlesTest < ActiveSupport::TestCase | @@ -180,6 +215,29 @@ class ArticlesTest < ActiveSupport::TestCase | ||
| 180 | json = JSON.parse(last_response.body) | 215 | json = JSON.parse(last_response.body) |
| 181 | assert_not_includes json['articles'].map {|a| a['id']}, article.id | 216 | assert_not_includes json['articles'].map {|a| a['id']}, article.id |
| 182 | end | 217 | end |
| 218 | + | ||
| 219 | + should "return article by #{kind} and path" do | ||
| 220 | + profile = fast_create(kind.camelcase.constantize, :environment_id => environment.id) | ||
| 221 | + parent_article = Folder.create!(:profile => profile, :name => "Parent Folder") | ||
| 222 | + article = Article.create!(:profile => profile, :name => "Some thing", :parent => parent_article) | ||
| 223 | + | ||
| 224 | + params[:path] = parent_article.slug+'/'+article.slug | ||
| 225 | + get "/api/v1/#{kind.pluralize}/#{profile.id}/articles?#{params.to_query}" | ||
| 226 | + json = JSON.parse(last_response.body) | ||
| 227 | + assert_equal article.id, json["article"]["id"] | ||
| 228 | + end | ||
| 229 | + | ||
| 230 | + should "not return article by #{kind} and path if user has no permission to view it" do | ||
| 231 | + profile = fast_create(kind.camelcase.constantize, :environment_id => environment.id) | ||
| 232 | + parent_article = Folder.create!(:profile => profile, :name => "Parent Folder") | ||
| 233 | + article = Article.create!(:profile => profile, :name => "Some thing", :parent => parent_article, :published => false) | ||
| 234 | + | ||
| 235 | + assert !article.published? | ||
| 236 | + | ||
| 237 | + params[:path] = parent_article.slug+'/'+article.slug | ||
| 238 | + get "/api/v1/#{kind.pluralize}/#{profile.id}/articles?#{params.to_query}" | ||
| 239 | + assert_equal 403, last_response.status | ||
| 240 | + end | ||
| 183 | end | 241 | end |
| 184 | 242 | ||
| 185 | ############################# | 243 | ############################# |
| @@ -0,0 +1,107 @@ | @@ -0,0 +1,107 @@ | ||
| 1 | +require File.dirname(__FILE__) + '/test_helper' | ||
| 2 | + | ||
| 3 | +class SearchTest < ActiveSupport::TestCase | ||
| 4 | + | ||
| 5 | + def create_article_with_optional_category(name, profile, category = nil) | ||
| 6 | + fast_create(Article, {:name => name, :profile_id => profile.id }, :search => true, :category => category, :title => name) | ||
| 7 | + end | ||
| 8 | + | ||
| 9 | + should 'not list unpublished articles' do | ||
| 10 | + person = fast_create(Person) | ||
| 11 | + article = fast_create(Article, :profile_id => person.id, :name => "Some thing", :published => false) | ||
| 12 | + assert !article.published? | ||
| 13 | + get "/api/v1/search/article" | ||
| 14 | + json = JSON.parse(last_response.body) | ||
| 15 | + assert_empty json['articles'] | ||
| 16 | + end | ||
| 17 | + | ||
| 18 | + should 'list articles' do | ||
| 19 | + person = fast_create(Person) | ||
| 20 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 21 | + get "/api/v1/search/article" | ||
| 22 | + json = JSON.parse(last_response.body) | ||
| 23 | + assert_not_empty json['articles'] | ||
| 24 | + end | ||
| 25 | + | ||
| 26 | + should 'invalid search string articles' do | ||
| 27 | + person = fast_create(Person) | ||
| 28 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 29 | + get "/api/v1/search/article?query=test" | ||
| 30 | + json = JSON.parse(last_response.body) | ||
| 31 | + assert_empty json['articles'] | ||
| 32 | + end | ||
| 33 | + | ||
| 34 | + should 'do not list articles of wrong type' do | ||
| 35 | + person = fast_create(Person) | ||
| 36 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 37 | + get "/api/v1/search/article?type=TinyMceArticle" | ||
| 38 | + json = JSON.parse(last_response.body) | ||
| 39 | + assert_empty json['articles'] | ||
| 40 | + end | ||
| 41 | + | ||
| 42 | + should 'list articles of one type' do | ||
| 43 | + person = fast_create(Person) | ||
| 44 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 45 | + article = fast_create(TinyMceArticle, :profile_id => person.id, :name => "Some thing", :published => true) | ||
| 46 | + get "/api/v1/search/article?type=TinyMceArticle" | ||
| 47 | + json = JSON.parse(last_response.body) | ||
| 48 | + assert_equal 1, json['articles'].count | ||
| 49 | + end | ||
| 50 | + | ||
| 51 | + should 'list articles of one type and query string' do | ||
| 52 | + person = fast_create(Person) | ||
| 53 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 54 | + art1 = create_article_with_optional_category('article for query', person) | ||
| 55 | + article = fast_create(TinyMceArticle, :profile_id => person.id, :name => "Some thing", :published => true) | ||
| 56 | + get "/api/v1/search/article?type=TinyMceArticle&query=thing" | ||
| 57 | + json = JSON.parse(last_response.body) | ||
| 58 | + assert_equal 1, json['articles'].count | ||
| 59 | + end | ||
| 60 | + | ||
| 61 | + should 'not return more entries than page limit' do | ||
| 62 | + person = fast_create(Person) | ||
| 63 | + ('1'..'5').each do |n| | ||
| 64 | + art = create_article_with_optional_category("Article #{n}", person) | ||
| 65 | + end | ||
| 66 | + | ||
| 67 | + get "/api/v1/search/article?query=Article&per_page=3" | ||
| 68 | + json = JSON.parse(last_response.body) | ||
| 69 | + | ||
| 70 | + assert_equal 3, json['articles'].count | ||
| 71 | + end | ||
| 72 | + | ||
| 73 | + should 'return entries second page' do | ||
| 74 | + person = fast_create(Person) | ||
| 75 | + ('1'..'5').each do |n| | ||
| 76 | + art = create_article_with_optional_category("Article #{n}", person) | ||
| 77 | + end | ||
| 78 | + | ||
| 79 | + get "/api/v1/search/article?query=Article&per_page=3&page=2" | ||
| 80 | + json = JSON.parse(last_response.body) | ||
| 81 | + | ||
| 82 | + assert_equal 2, json['articles'].count | ||
| 83 | + end | ||
| 84 | + | ||
| 85 | + should 'search articles in profile' do | ||
| 86 | + person1 = fast_create(Person) | ||
| 87 | + person2 = fast_create(Person) | ||
| 88 | + | ||
| 89 | + art1 = create_article_with_optional_category("Article 1 for profile #{person1.id}", person1) | ||
| 90 | + art2 = create_article_with_optional_category("Article for profile #{person2.id}", person2) | ||
| 91 | + art3 = create_article_with_optional_category("Article 2 for profile #{person1.id}", person1) | ||
| 92 | + | ||
| 93 | + get "/api/v1/search/article?query=Article&profile_id=#{person1.id}" | ||
| 94 | + json = JSON.parse(last_response.body) | ||
| 95 | + # Only for person1 | ||
| 96 | + assert_equal 2, json['articles'].count | ||
| 97 | + end | ||
| 98 | + | ||
| 99 | + should 'search with fields' do | ||
| 100 | + person = fast_create(Person) | ||
| 101 | + art = create_article_with_optional_category('an article to be found', person) | ||
| 102 | + get "/api/v1/search/article?fields=title" | ||
| 103 | + json = JSON.parse(last_response.body) | ||
| 104 | + assert_not_empty json['articles'] | ||
| 105 | + assert_equal ['title'], json['articles'].first.keys | ||
| 106 | + end | ||
| 107 | +end | ||
| 0 | \ No newline at end of file | 108 | \ No newline at end of file |
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 |