From f04df39e5360a895b9e1c15d3ed169901a2e97ef Mon Sep 17 00:00:00 2001 From: Leandro Nunes dos Santos Date: Tue, 24 Mar 2015 22:53:45 -0300 Subject: [PATCH] api: refactoring api to put it at noosfero module --- Rakefile | 2 +- config.ru | 5 +---- lib/api/api.rb | 38 -------------------------------------- lib/api/entities.rb | 92 -------------------------------------------------------------------------------------------- lib/api/helpers.rb | 187 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- lib/api/session.rb | 48 ------------------------------------------------ lib/api/v1/articles.rb | 167 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- lib/api/v1/categories.rb | 23 ----------------------- lib/api/v1/comments.rb | 45 --------------------------------------------- lib/api/v1/communities.rb | 70 ---------------------------------------------------------------------- lib/api/v1/enterprises.rb | 54 ------------------------------------------------------ lib/api/v1/people.rb | 40 ---------------------------------------- lib/api/v1/users.rb | 46 ---------------------------------------------- lib/noosfero/api/api.rb | 39 +++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/entities.rb | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/helpers.rb | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/session.rb | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/articles.rb | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/categories.rb | 25 +++++++++++++++++++++++++ lib/noosfero/api/v1/comments.rb | 47 +++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/communities.rb | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/enterprises.rb | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/people.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/noosfero/api/v1/users.rb | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/api/helpers_test.rb | 3 ++- test/unit/api/test_helper.rb | 2 +- 26 files changed, 836 insertions(+), 817 deletions(-) delete mode 100644 lib/api/api.rb delete mode 100644 lib/api/entities.rb delete mode 100644 lib/api/helpers.rb delete mode 100644 lib/api/session.rb delete mode 100644 lib/api/v1/articles.rb delete mode 100644 lib/api/v1/categories.rb delete mode 100644 lib/api/v1/comments.rb delete mode 100644 lib/api/v1/communities.rb delete mode 100644 lib/api/v1/enterprises.rb delete mode 100644 lib/api/v1/people.rb delete mode 100644 lib/api/v1/users.rb create mode 100644 lib/noosfero/api/api.rb create mode 100644 lib/noosfero/api/entities.rb create mode 100644 lib/noosfero/api/helpers.rb create mode 100644 lib/noosfero/api/session.rb create mode 100644 lib/noosfero/api/v1/articles.rb create mode 100644 lib/noosfero/api/v1/categories.rb create mode 100644 lib/noosfero/api/v1/comments.rb create mode 100644 lib/noosfero/api/v1/communities.rb create mode 100644 lib/noosfero/api/v1/enterprises.rb create mode 100644 lib/noosfero/api/v1/people.rb create mode 100644 lib/noosfero/api/v1/users.rb diff --git a/Rakefile b/Rakefile index f27bc4e..5d8df4a 100644 --- a/Rakefile +++ b/Rakefile @@ -26,7 +26,7 @@ plugins_tasks.each{ |ext| load ext } desc "Print out grape routes" task :grape_routes => :environment do #require 'api/api.rb' - API::API.routes.each do |route| + Noosfero::API::API.routes.each do |route| puts route method = route.route_method path = route.route_path diff --git a/config.ru b/config.ru index 636cb4a..268ae17 100644 --- a/config.ru +++ b/config.ru @@ -1,9 +1,6 @@ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) -run Noosfero::Application -require "config/environment" - #use Rails::Rack::LogTailer #use Rails::Rack::Static @@ -14,6 +11,6 @@ rails_app = Rack::Builder.new do end run Rack::Cascade.new([ - API::API, + Noosfero::API::API, rails_app ]) diff --git a/lib/api/api.rb b/lib/api/api.rb deleted file mode 100644 index fe39ea3..0000000 --- a/lib/api/api.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'grape' -Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} - -module API - class API < Grape::API - before { start_log } - before { setup_multitenancy } - before { detect_stuff_by_domain } - after { end_log } - - version 'v1' - prefix "api" - format :json - content_type :txt, "text/plain" - - helpers APIHelpers - - mount V1::Articles - mount V1::Comments - mount V1::Users - mount V1::Communities - mount V1::People - mount V1::Enterprises - mount V1::Categories - mount Session - - # hook point which allow plugins to add Grape::API extensions to API::API - #finds for plugins which has api mount points classes defined (the class should extends Grape::API) - @plugins = Noosfero::Plugin.all.map { |p| p.constantize } - @plugins.each do |klass| - if klass.public_methods.include? 'api_mount_points' - klass.api_mount_points.each do |mount_class| - mount mount_class if mount_class && ( mount_class < Grape::API ) - end - end - end - end -end diff --git a/lib/api/entities.rb b/lib/api/entities.rb deleted file mode 100644 index b5c55b2..0000000 --- a/lib/api/entities.rb +++ /dev/null @@ -1,92 +0,0 @@ -module API - module Entities - - Grape::Entity.format_with :timestamp do |date| - date.strftime('%Y/%m/%d %H:%M:%S') if date - end - - class Image < Grape::Entity - root 'images', 'image' - - expose :icon_url do |image, options| - image.public_filename(:icon) - end - - expose :minor_url do |image, options| - image.public_filename(:minor) - end - - expose :portrait_url do |image, options| - image.public_filename(:portrait) - end - - expose :thumb_url do |image, options| - image.public_filename(:thumb) - end - end - - class Profile < Grape::Entity - expose :identifier, :name, :id - expose :created_at, :format_with => :timestamp - expose :image, :using => Image - end - - class Person < Profile - root 'people', 'person' - end - class Enterprise < Profile - root 'enterprises', 'enterprise' - end - class Community < Profile - root 'communities', 'community' - expose :description - end - - class Category < Grape::Entity - root 'categories', 'category' - expose :name, :id, :slug - expose :image, :using => Image - end - - - class Article < Grape::Entity - root 'articles', 'article' - expose :id, :body - expose :created_at, :format_with => :timestamp - expose :title, :documentation => {:type => "String", :desc => "Title of the article"} - expose :created_by, :as => :author, :using => Profile - expose :profile, :using => Profile - expose :categories, :using => Category - expose :parent, :using => Article - end - - class Comment < Grape::Entity - root 'comments', 'comment' - expose :body, :title, :id - expose :created_at, :format_with => :timestamp - expose :author, :using => Profile - end - - - class User < Grape::Entity - root 'users', 'user' - expose :id - expose :login - expose :person, :using => Profile - expose :permissions do |user, options| - output = {} - user.person.role_assignments.map do |role_assigment| - if role_assigment.resource.respond_to?(:identifier) - output[role_assigment.resource.identifier] = role_assigment.role.permissions - end - end - output - end - end - - class UserLogin < User - expose :private_token - end - - end -end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb deleted file mode 100644 index 1eed9be..0000000 --- a/lib/api/helpers.rb +++ /dev/null @@ -1,187 +0,0 @@ -module API - module APIHelpers - PRIVATE_TOKEN_PARAM = :private_token - - def logger - @logger ||= Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_api.log")) - end - - def current_user - private_token = params[PRIVATE_TOKEN_PARAM].to_s if params - @current_user ||= User.find_by_private_token(private_token) - @current_user = nil if !@current_user.nil? && @current_user.private_token_expired? - @current_user - end - - def current_person - current_user.person unless current_user.nil? - end - - def logout - @current_user = nil - end - - def environment - @environment - end - - def limit - limit = params[:limit].to_i - limit = default_limit if limit <= 0 - limit - end - - def period(from_date, until_date) - return nil if from_date.nil? && until_date.nil? - - begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date - end_period = until_date.nil? ? DateTime.now : until_date - - begin_period..end_period - end - - def parse_content_type(content_type) - return nil if content_type.blank? - content_type.split(',').map do |content_type| - content_type.camelcase - end - end - - def find_article(articles, id) - article = articles.find(id) - article.display_to?(current_user.person) ? article : forbidden! - end - - def make_conditions_with_parameter(params = {}) - conditions = {} - from_date = DateTime.parse(params[:from]) if params[:from] - until_date = DateTime.parse(params[:until]) if params[:until] - - conditions[:type] = parse_content_type(params[:content_type]) unless params[:content_type].nil? - - conditions[:created_at] = period(from_date, until_date) if from_date || until_date - - conditions - end - - - def select_filtered_collection_of(object, method, params) - conditions = make_conditions_with_parameter(params) - - if params[:reference_id] - objects = object.send(method).send("#{params.key?(:oldest) ? 'older_than' : 'newer_than'}", params[:reference_id]).where(conditions).limit(limit).order("created_at DESC") - else - objects = object.send(method).where(conditions).limit(limit).order("created_at DESC") - end - objects - end - - def authenticate! - unauthorized! unless current_user - end - - # Checks the occurrences of uniqueness of attributes, each attribute must be present in the params hash - # or a Bad Request error is invoked. - # - # Parameters: - # keys (unique) - A hash consisting of keys that must be unique - def unique_attributes!(obj, keys) - keys.each do |key| - cant_be_saved_request!(key) if obj.send("find_by_#{key.to_s}", params[key]) - end - end - - def attributes_for_keys(keys) - attrs = {} - keys.each do |key| - attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) - end - attrs - end - - ########################################## - # error helpers # - ########################################## - - def forbidden! - render_api_error!('403 Forbidden', 403) - end - - def cant_be_saved_request!(attribute) - message = _("(Invalid request) #{attribute} can't be saved") - render_api_error!(message, 400) - end - - def bad_request!(attribute) - message = _("(Bad request) #{attribute} not given") - render_api_error!(message, 400) - end - - def something_wrong! - message = _("Something wrong happened") - render_api_error!(message, 400) - end - - def unauthorized! - render_api_error!(_('Unauthorized'), 401) - end - - def not_allowed! - render_api_error!(_('Method Not Allowed'), 405) - end - - def render_api_error!(message, status) - error!({'message' => message, :code => status}, status) - end - - def render_api_errors!(messages) - render_api_error!(messages.join(','), 400) - end - protected - - def start_log - logger.info "Started #{request.path} #{request.params.except('password')}" - end - def end_log - logger.info "Completed #{request.path}" - end - - def setup_multitenancy - Noosfero::MultiTenancy.setup!(request.host) - end - - def detect_stuff_by_domain - @domain = Domain.find_by_name(request.host) - if @domain.nil? - @environment = Environment.default - if @environment.nil? && Rails.env.development? - # This should only happen in development ... - @environment = Environment.create!(:name => "Noosfero", :is_default => true) - end - else - @environment = @domain.environment - end - end - - private - - def default_limit - 20 - end - - def parse_content_type(content_type) - return nil if content_type.blank? - content_type.split(',').map do |content_type| - content_type.camelcase - end - end - - def period(from_date, until_date) - begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date - end_period = until_date.nil? ? DateTime.now : until_date - - begin_period..end_period - end - - end -end diff --git a/lib/api/session.rb b/lib/api/session.rb deleted file mode 100644 index c9d6363..0000000 --- a/lib/api/session.rb +++ /dev/null @@ -1,48 +0,0 @@ -module API - - class Session < Grape::API - - # Login to get token - # - # Parameters: - # login (*required) - user login or email - # password (required) - user password - # - # Example Request: - # POST http://localhost:3000/api/v1/login?login=adminuser&password=admin - post "/login" do - user ||= User.authenticate(params[:login], params[:password], environment) - - return unauthorized! unless user - user.generate_private_token! - present user, :with => Entities::UserLogin - end - - # Create user. - # - # Parameters: - # email (required) - Email - # password (required) - Password - # login - login - # Example Request: - # POST /register?email=some@mail.com&password=pas&login=some - params do - requires :email, type: String, desc: _("Email") - requires :login, type: String, desc: _("Login") - requires :password, type: String, desc: _("Password") - end - post "/register" do - unique_attributes! User, [:email, :login] - attrs = attributes_for_keys [:email, :login, :password] - attrs[:password_confirmation] = attrs[:password] - user = User.new(attrs) - if user.save - user.activate - present user, :with => Entities::User - else - something_wrong! - end - end - - end -end diff --git a/lib/api/v1/articles.rb b/lib/api/v1/articles.rb deleted file mode 100644 index 4ae5411..0000000 --- a/lib/api/v1/articles.rb +++ /dev/null @@ -1,167 +0,0 @@ -module API - module V1 - class Articles < Grape::API - before { authenticate! } - - ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} - - resource :articles do - - # Collect articles - # - # Parameters: - # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created - # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected - # limit - amount of articles returned. The default value is 20 - # - # Example Request: - # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317 - get do - articles = select_filtered_collection_of(environment, 'articles', params) - articles = articles.display_filter(current_person, nil) - present articles, :with => Entities::Article - end - - desc "Return the article id" - get ':id' do - article = find_article(environment.articles, params[:id]) - present article, :with => Entities::Article - end - - get ':id/children' do - article = find_article(environment.articles, params[:id]) - articles = select_filtered_collection_of(article, 'children', params) - articles = articles.display_filter(current_person, nil) - present articles, :with => Entities::Article - end - - get ':id/children/:child_id' do - article = find_article(environment.articles, params[:id]) - present find_article(article.children, params[:child_id]), :with => Entities::Article - end - - end - - resource :communities do - segment '/:community_id' do - resource :articles do - get do - community = environment.communities.find(params[:community_id]) - articles = select_filtered_collection_of(community, 'articles', params) - articles = articles.display_filter(current_person, community) - present articles, :with => Entities::Article - end - - get ':id' do - community = environment.communities.find(params[:community_id]) - article = find_article(community.articles, params[:id]) - present article, :with => Entities::Article - end - - # Example Request: - # POST api/v1/communites/:community_id/articles?private_token=234298743290432&article[name]=title&article[body]=body - post do - community = environment.communities.find(params[:community_id]) - return forbidden! unless current_person.can_post_content?(community) - - klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] - return forbidden! unless ARTICLE_TYPES.include?(klass_type) - - article = klass_type.constantize.new(params[:article]) - article.last_changed_by = current_person - article.created_by= current_person - article.profile = community - - if !article.save - render_api_errors!(article.errors.full_messages) - end - present article, :with => Entities::Article - end - - end - end - - end - - resource :people do - segment '/:person_id' do - resource :articles do - get do - person = environment.people.find(params[:person_id]) - articles = select_filtered_collection_of(person, 'articles', params) - articles = articles.display_filter(current_person, person) - present articles, :with => Entities::Article - end - - get ':id' do - person = environment.people.find(params[:person_id]) - article = find_article(person.articles, params[:id]) - present article, :with => Entities::Article - end - - post do - person = environment.people.find(params[:person_id]) - return forbidden! unless current_person.can_post_content?(person) - - klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] - return forbidden! unless ARTICLE_TYPES.include?(klass_type) - - article = klass_type.constantize.new(params[:article]) - article.last_changed_by = current_person - article.created_by= current_person - article.profile = person - - if !article.save - render_api_errors!(article.errors.full_messages) - end - present article, :with => Entities::Article - end - - end - end - - end - - resource :enterprises do - segment '/:enterprise_id' do - resource :articles do - get do - enterprise = environment.enterprises.find(params[:enterprise_id]) - articles = select_filtered_collection_of(enterprise, 'articles', params) - articles = articles.display_filter(current_person, enterprise) - present articles, :with => Entities::Article - end - - get ':id' do - enterprise = environment.enterprises.find(params[:enterprise_id]) - article = find_article(enterprise.articles, params[:id]) - present article, :with => Entities::Article - end - - post do - enterprise = environment.enterprises.find(params[:enterprise_id]) - return forbidden! unless current_person.can_post_content?(enterprise) - - klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] - return forbidden! unless ARTICLE_TYPES.include?(klass_type) - - article = klass_type.constantize.new(params[:article]) - article.last_changed_by = current_person - article.created_by= current_person - article.profile = enterprise - - if !article.save - render_api_errors!(article.errors.full_messages) - end - present article, :with => Entities::Article - end - - end - end - - end - - - end - end -end diff --git a/lib/api/v1/categories.rb b/lib/api/v1/categories.rb deleted file mode 100644 index 8250bca..0000000 --- a/lib/api/v1/categories.rb +++ /dev/null @@ -1,23 +0,0 @@ -module API - module V1 - class Categories < Grape::API - before { authenticate! } - - resource :categories do - - get do - type = params[:category_type] - categories = type.nil? ? environment.categories : environment.categories.find(:all, :conditions => {:type => type}) - present categories, :with => Entities::Category - end - - desc "Return the category by id" - get ':id' do - present environment.categories.find(params[:id]), :with => Entities::Category - end - - end - - end - end -end diff --git a/lib/api/v1/comments.rb b/lib/api/v1/comments.rb deleted file mode 100644 index a701886..0000000 --- a/lib/api/v1/comments.rb +++ /dev/null @@ -1,45 +0,0 @@ -module API - module V1 - class Comments < Grape::API - before { authenticate! } - - resource :articles do - # Collect comments from articles - # - # Parameters: - # reference_id - comment id used as reference to collect comment - # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected - # limit - amount of comments returned. The default value is 20 - # - # Example Request: - # GET /articles/12/comments?oldest&limit=10&reference_id=23 - get ":id/comments" do - - conditions = make_conditions_with_parameter(params) - article = find_article(environment.articles, params[:id]) - - if params[:reference_id] - comments = article.comments.send("#{params.key?(:oldest) ? 'older_than' : 'newer_than'}", params[:reference_id]).reorder("created_at DESC").find(:all, :conditions => conditions, :limit => limit) - else - comments = article.comments.reorder("created_at DESC").find(:all, :conditions => conditions, :limit => limit) - end - present comments, :with => Entities::Comment - - end - - get ":id/comments/:comment_id" do - article = find_article(environment.articles, params[:id]) - present article.comments.find(params[:comment_id]), :with => Entities::Comment - end - - # Example Request: - # POST api/v1/articles/12/comments?private_toke=234298743290432&body=new comment - post ":id/comments" do - article = find_article(environment.articles, params[:id]) - present article.comments.create(:author => current_person, :body => params[:body]), :with => Entities::Comment - end - end - - end - end -end diff --git a/lib/api/v1/communities.rb b/lib/api/v1/communities.rb deleted file mode 100644 index 17f11bd..0000000 --- a/lib/api/v1/communities.rb +++ /dev/null @@ -1,70 +0,0 @@ -module API - module V1 - class Communities < Grape::API - before { authenticate! } - - resource :communities do - - # Collect comments from articles - # - # Parameters: - # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created - # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected - # limit - amount of comments returned. The default value is 20 - # - # Example Request: - # GET /communities?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 - # GET /communities?reference_id=10&limit=10&oldest - get do - communities = select_filtered_collection_of(environment, 'communities', params) - communities = communities.visible_for_person(current_person) - present communities, :with => Entities::Community - end - - - # Example Request: - # POST api/v1/communties?private_token=234298743290432&community[name]=some_name - post do - params[:community] ||= {} - begin - community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment})) - rescue - community = Community.new(params[:community]) - end - - if !community.save - render_api_errors!(community.errors.full_messages) - end - - present community, :with => Entities::Community - end - - get ':id' do - community = environment.communities.visible.find_by_id(params[:id]) - present community, :with => Entities::Community - end - - end - - resource :people do - - segment '/:person_id' do - - resource :communities do - - get do - person = environment.people.find(params[:person_id]) - communities = select_filtered_collection_of(person, 'communities', params) - communities = communities.visible - present communities, :with => Entities::Community - end - - end - - end - - end - - end - end -end diff --git a/lib/api/v1/enterprises.rb b/lib/api/v1/enterprises.rb deleted file mode 100644 index 670af84..0000000 --- a/lib/api/v1/enterprises.rb +++ /dev/null @@ -1,54 +0,0 @@ -module API - module V1 - class Enterprises < Grape::API - before { authenticate! } - - resource :enterprises do - - # Collect comments from articles - # - # Parameters: - # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created - # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected - # limit - amount of comments returned. The default value is 20 - # - # Example Request: - # GET /enterprises?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 - # GET /enterprises?reference_id=10&limit=10&oldest - get do - enterprises = select_filtered_collection_of(environment, 'enterprises', params) - enterprises = enterprises.visible_for_person(current_person) - present enterprises, :with => Entities::Enterprise - end - - desc "Return one enterprise by id" - get ':id' do - enterprise = environment.enterprises.visible.find_by_id(params[:id]) - present enterprise, :with => Entities::Enterprise - end - - end - - resource :people do - - segment '/:person_id' do - - resource :enterprises do - - get do - person = environment.people.find(params[:person_id]) - enterprises = select_filtered_collection_of(person, 'enterprises', params) - enterprises = enterprises.visible - present enterprises, :with => Entities::Enterprise - end - - end - - end - - end - - - end - end -end diff --git a/lib/api/v1/people.rb b/lib/api/v1/people.rb deleted file mode 100644 index 669910d..0000000 --- a/lib/api/v1/people.rb +++ /dev/null @@ -1,40 +0,0 @@ -module API - module V1 - class People < Grape::API - before { authenticate! } - - resource :people do - - # Collect comments from articles - # - # Parameters: - # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created - # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected - # limit - amount of comments returned. The default value is 20 - # - # Example Request: - # GET /people?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 - # GET /people?reference_id=10&limit=10&oldest - get do - people = select_filtered_collection_of(environment, 'people', params) - people = people.visible_for_person(current_person) - present people, :with => Entities::Person - end - - desc "Return the person information" - get ':id' do - person = environment.people.visible.find_by_id(params[:id]) - present person, :with => Entities::Person - end - - desc "Return the person friends" - get ':id/friends' do - friends = current_person.friends.visible - present friends, :with => Entities::Person - end - - end - - end - end -end diff --git a/lib/api/v1/users.rb b/lib/api/v1/users.rb deleted file mode 100644 index 69fe2e2..0000000 --- a/lib/api/v1/users.rb +++ /dev/null @@ -1,46 +0,0 @@ -module API - module V1 - class Users < Grape::API - before { authenticate! } - - resource :users do - - #FIXME make the pagination - #FIXME put it on environment context - get do - present environment.users, :with => Entities::User - end - - # Example Request: - # POST api/v1/users?user[login]=some_login&user[password]=some - post do - user = User.new(params[:user]) - user.terms_of_use = environment.terms_of_use - user.environment = environment - if !user.save - render_api_errors!(user.errors.full_messages) - end - - present user, :with => Entities::User - end - - get ":id" do - present environment.users.find_by_id(params[:id]), :with => Entities::User - end - - get ":id/permissions" do - user = environment.users.find(params[:id]) - output = {} - user.person.role_assignments.map do |role_assigment| - if role_assigment.resource.respond_to?(:identifier) && role_assigment.resource.identifier == params[:profile] - output[:permissions] = role_assigment.role.permissions - end - end - present output - end - - end - - end - end -end diff --git a/lib/noosfero/api/api.rb b/lib/noosfero/api/api.rb new file mode 100644 index 0000000..4d2927e --- /dev/null +++ b/lib/noosfero/api/api.rb @@ -0,0 +1,39 @@ +require 'grape' +Dir["#{Rails.root}/lib/noosfero/api/*.rb"].each {|file| require file} +module Noosfero + module API + class API < Grape::API + before { start_log } + before { setup_multitenancy } + before { detect_stuff_by_domain } + after { end_log } + + version 'v1' + prefix "api" + format :json + content_type :txt, "text/plain" + + helpers APIHelpers + + mount V1::Articles + mount V1::Comments + mount V1::Users + mount V1::Communities + mount V1::People + mount V1::Enterprises + mount V1::Categories + mount Session + + # hook point which allow plugins to add Grape::API extensions to API::API + #finds for plugins which has api mount points classes defined (the class should extends Grape::API) + @plugins = Noosfero::Plugin.all.map { |p| p.constantize } + @plugins.each do |klass| + if klass.public_methods.include? 'api_mount_points' + klass.api_mount_points.each do |mount_class| + mount mount_class if mount_class && ( mount_class < Grape::API ) + end + end + end + end + end +end diff --git a/lib/noosfero/api/entities.rb b/lib/noosfero/api/entities.rb new file mode 100644 index 0000000..36586ed --- /dev/null +++ b/lib/noosfero/api/entities.rb @@ -0,0 +1,94 @@ +module Noosfero + module API + module Entities + + Grape::Entity.format_with :timestamp do |date| + date.strftime('%Y/%m/%d %H:%M:%S') if date + end + + class Image < Grape::Entity + root 'images', 'image' + + expose :icon_url do |image, options| + image.public_filename(:icon) + end + + expose :minor_url do |image, options| + image.public_filename(:minor) + end + + expose :portrait_url do |image, options| + image.public_filename(:portrait) + end + + expose :thumb_url do |image, options| + image.public_filename(:thumb) + end + end + + class Profile < Grape::Entity + expose :identifier, :name, :id + expose :created_at, :format_with => :timestamp + expose :image, :using => Image + end + + class Person < Profile + root 'people', 'person' + end + class Enterprise < Profile + root 'enterprises', 'enterprise' + end + class Community < Profile + root 'communities', 'community' + expose :description + end + + class Category < Grape::Entity + root 'categories', 'category' + expose :name, :id, :slug + expose :image, :using => Image + end + + + class Article < Grape::Entity + root 'articles', 'article' + expose :id, :body + expose :created_at, :format_with => :timestamp + expose :title, :documentation => {:type => "String", :desc => "Title of the article"} + expose :created_by, :as => :author, :using => Profile + expose :profile, :using => Profile + expose :categories, :using => Category + expose :parent, :using => Article + end + + class Comment < Grape::Entity + root 'comments', 'comment' + expose :body, :title, :id + expose :created_at, :format_with => :timestamp + expose :author, :using => Profile + end + + + class User < Grape::Entity + root 'users', 'user' + expose :id + expose :login + expose :person, :using => Profile + expose :permissions do |user, options| + output = {} + user.person.role_assignments.map do |role_assigment| + if role_assigment.resource.respond_to?(:identifier) + output[role_assigment.resource.identifier] = role_assigment.role.permissions + end + end + output + end + end + + class UserLogin < User + expose :private_token + end + + end + end +end diff --git a/lib/noosfero/api/helpers.rb b/lib/noosfero/api/helpers.rb new file mode 100644 index 0000000..cfb941e --- /dev/null +++ b/lib/noosfero/api/helpers.rb @@ -0,0 +1,189 @@ +module Noosfero + module API + module APIHelpers + PRIVATE_TOKEN_PARAM = :private_token + + def logger + @logger ||= Logger.new(File.join(Rails.root, 'log', "#{ENV['RAILS_ENV']}_api.log")) + end + + def current_user + private_token = params[PRIVATE_TOKEN_PARAM].to_s if params + @current_user ||= User.find_by_private_token(private_token) + @current_user = nil if !@current_user.nil? && @current_user.private_token_expired? + @current_user + end + + def current_person + current_user.person unless current_user.nil? + end + + def logout + @current_user = nil + end + + def environment + @environment + end + + def limit + limit = params[:limit].to_i + limit = default_limit if limit <= 0 + limit + end + + def period(from_date, until_date) + return nil if from_date.nil? && until_date.nil? + + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date + end_period = until_date.nil? ? DateTime.now : until_date + + begin_period..end_period + end + + def parse_content_type(content_type) + return nil if content_type.blank? + content_type.split(',').map do |content_type| + content_type.camelcase + end + end + + def find_article(articles, id) + article = articles.find(id) + article.display_to?(current_user.person) ? article : forbidden! + end + + def make_conditions_with_parameter(params = {}) + conditions = {} + from_date = DateTime.parse(params[:from]) if params[:from] + until_date = DateTime.parse(params[:until]) if params[:until] + + conditions[:type] = parse_content_type(params[:content_type]) unless params[:content_type].nil? + + conditions[:created_at] = period(from_date, until_date) if from_date || until_date + + conditions + end + + + def select_filtered_collection_of(object, method, params) + conditions = make_conditions_with_parameter(params) + + if params[:reference_id] + objects = object.send(method).send("#{params.key?(:oldest) ? 'older_than' : 'newer_than'}", params[:reference_id]).where(conditions).limit(limit).order("created_at DESC") + else + objects = object.send(method).where(conditions).limit(limit).order("created_at DESC") + end + objects + end + + def authenticate! + unauthorized! unless current_user + end + + # Checks the occurrences of uniqueness of attributes, each attribute must be present in the params hash + # or a Bad Request error is invoked. + # + # Parameters: + # keys (unique) - A hash consisting of keys that must be unique + def unique_attributes!(obj, keys) + keys.each do |key| + cant_be_saved_request!(key) if obj.send("find_by_#{key.to_s}", params[key]) + end + end + + def attributes_for_keys(keys) + attrs = {} + keys.each do |key| + attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) + end + attrs + end + + ########################################## + # error helpers # + ########################################## + + def forbidden! + render_api_error!('403 Forbidden', 403) + end + + def cant_be_saved_request!(attribute) + message = _("(Invalid request) #{attribute} can't be saved") + render_api_error!(message, 400) + end + + def bad_request!(attribute) + message = _("(Bad request) #{attribute} not given") + render_api_error!(message, 400) + end + + def something_wrong! + message = _("Something wrong happened") + render_api_error!(message, 400) + end + + def unauthorized! + render_api_error!(_('Unauthorized'), 401) + end + + def not_allowed! + render_api_error!(_('Method Not Allowed'), 405) + end + + def render_api_error!(message, status) + error!({'message' => message, :code => status}, status) + end + + def render_api_errors!(messages) + render_api_error!(messages.join(','), 400) + end + protected + + def start_log + logger.info "Started #{request.path} #{request.params.except('password')}" + end + def end_log + logger.info "Completed #{request.path}" + end + + def setup_multitenancy + Noosfero::MultiTenancy.setup!(request.host) + end + + def detect_stuff_by_domain + @domain = Domain.find_by_name(request.host) + if @domain.nil? + @environment = Environment.default + if @environment.nil? && Rails.env.development? + # This should only happen in development ... + @environment = Environment.create!(:name => "Noosfero", :is_default => true) + end + else + @environment = @domain.environment + end + end + + private + + def default_limit + 20 + end + + def parse_content_type(content_type) + return nil if content_type.blank? + content_type.split(',').map do |content_type| + content_type.camelcase + end + end + + def period(from_date, until_date) + begin_period = from_date.nil? ? Time.at(0).to_datetime : from_date + end_period = until_date.nil? ? DateTime.now : until_date + + begin_period..end_period + end + + end + end +end diff --git a/lib/noosfero/api/session.rb b/lib/noosfero/api/session.rb new file mode 100644 index 0000000..686eff1 --- /dev/null +++ b/lib/noosfero/api/session.rb @@ -0,0 +1,50 @@ +module Noosfero + module API + + class Session < Grape::API + + # Login to get token + # + # Parameters: + # login (*required) - user login or email + # password (required) - user password + # + # Example Request: + # POST http://localhost:3000/api/v1/login?login=adminuser&password=admin + post "/login" do + user ||= User.authenticate(params[:login], params[:password], environment) + + return unauthorized! unless user + user.generate_private_token! + present user, :with => Entities::UserLogin + end + + # Create user. + # + # Parameters: + # email (required) - Email + # password (required) - Password + # login - login + # Example Request: + # POST /register?email=some@mail.com&password=pas&login=some + params do + requires :email, type: String, desc: _("Email") + requires :login, type: String, desc: _("Login") + requires :password, type: String, desc: _("Password") + end + post "/register" do + unique_attributes! User, [:email, :login] + attrs = attributes_for_keys [:email, :login, :password] + attrs[:password_confirmation] = attrs[:password] + user = User.new(attrs) + if user.save + user.activate + present user, :with => Entities::User + else + something_wrong! + end + end + + end + end +end diff --git a/lib/noosfero/api/v1/articles.rb b/lib/noosfero/api/v1/articles.rb new file mode 100644 index 0000000..842f39b --- /dev/null +++ b/lib/noosfero/api/v1/articles.rb @@ -0,0 +1,169 @@ +module Noosfero + module API + module V1 + class Articles < Grape::API + before { authenticate! } + + ARTICLE_TYPES = Article.descendants.map{|a| a.to_s} + + resource :articles do + + # Collect articles + # + # Parameters: + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created + # oldest - Collect the oldest articles. If nothing is passed the newest articles are collected + # limit - amount of articles returned. The default value is 20 + # + # Example Request: + # GET host/api/v1/articles?from=2013-04-04-14:41:43&until=2015-04-04-14:41:43&limit=10&private_token=e96fff37c2238fdab074d1dcea8e6317 + get do + articles = select_filtered_collection_of(environment, 'articles', params) + articles = articles.display_filter(current_person, nil) + present articles, :with => Entities::Article + end + + desc "Return the article id" + get ':id' do + article = find_article(environment.articles, params[:id]) + present article, :with => Entities::Article + end + + get ':id/children' do + article = find_article(environment.articles, params[:id]) + articles = select_filtered_collection_of(article, 'children', params) + articles = articles.display_filter(current_person, nil) + present articles, :with => Entities::Article + end + + get ':id/children/:child_id' do + article = find_article(environment.articles, params[:id]) + present find_article(article.children, params[:child_id]), :with => Entities::Article + end + + end + + resource :communities do + segment '/:community_id' do + resource :articles do + get do + community = environment.communities.find(params[:community_id]) + articles = select_filtered_collection_of(community, 'articles', params) + articles = articles.display_filter(current_person, community) + present articles, :with => Entities::Article + end + + get ':id' do + community = environment.communities.find(params[:community_id]) + article = find_article(community.articles, params[:id]) + present article, :with => Entities::Article + end + + # Example Request: + # POST api/v1/communites/:community_id/articles?private_token=234298743290432&article[name]=title&article[body]=body + post do + community = environment.communities.find(params[:community_id]) + return forbidden! unless current_person.can_post_content?(community) + + klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] + return forbidden! unless ARTICLE_TYPES.include?(klass_type) + + article = klass_type.constantize.new(params[:article]) + article.last_changed_by = current_person + article.created_by= current_person + article.profile = community + + if !article.save + render_api_errors!(article.errors.full_messages) + end + present article, :with => Entities::Article + end + + end + end + + end + + resource :people do + segment '/:person_id' do + resource :articles do + get do + person = environment.people.find(params[:person_id]) + articles = select_filtered_collection_of(person, 'articles', params) + articles = articles.display_filter(current_person, person) + present articles, :with => Entities::Article + end + + get ':id' do + person = environment.people.find(params[:person_id]) + article = find_article(person.articles, params[:id]) + present article, :with => Entities::Article + end + + post do + person = environment.people.find(params[:person_id]) + return forbidden! unless current_person.can_post_content?(person) + + klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] + return forbidden! unless ARTICLE_TYPES.include?(klass_type) + + article = klass_type.constantize.new(params[:article]) + article.last_changed_by = current_person + article.created_by= current_person + article.profile = person + + if !article.save + render_api_errors!(article.errors.full_messages) + end + present article, :with => Entities::Article + end + + end + end + + end + + resource :enterprises do + segment '/:enterprise_id' do + resource :articles do + get do + enterprise = environment.enterprises.find(params[:enterprise_id]) + articles = select_filtered_collection_of(enterprise, 'articles', params) + articles = articles.display_filter(current_person, enterprise) + present articles, :with => Entities::Article + end + + get ':id' do + enterprise = environment.enterprises.find(params[:enterprise_id]) + article = find_article(enterprise.articles, params[:id]) + present article, :with => Entities::Article + end + + post do + enterprise = environment.enterprises.find(params[:enterprise_id]) + return forbidden! unless current_person.can_post_content?(enterprise) + + klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type] + return forbidden! unless ARTICLE_TYPES.include?(klass_type) + + article = klass_type.constantize.new(params[:article]) + article.last_changed_by = current_person + article.created_by= current_person + article.profile = enterprise + + if !article.save + render_api_errors!(article.errors.full_messages) + end + present article, :with => Entities::Article + end + + end + end + + end + + + end + end + end +end diff --git a/lib/noosfero/api/v1/categories.rb b/lib/noosfero/api/v1/categories.rb new file mode 100644 index 0000000..1392707 --- /dev/null +++ b/lib/noosfero/api/v1/categories.rb @@ -0,0 +1,25 @@ +module Noosfero + module API + module V1 + class Categories < Grape::API + before { authenticate! } + + resource :categories do + + get do + type = params[:category_type] + categories = type.nil? ? environment.categories : environment.categories.find(:all, :conditions => {:type => type}) + present categories, :with => Entities::Category + end + + desc "Return the category by id" + get ':id' do + present environment.categories.find(params[:id]), :with => Entities::Category + end + + end + + end + end + end +end diff --git a/lib/noosfero/api/v1/comments.rb b/lib/noosfero/api/v1/comments.rb new file mode 100644 index 0000000..6b44f4c --- /dev/null +++ b/lib/noosfero/api/v1/comments.rb @@ -0,0 +1,47 @@ +module Noosfero + module API + module V1 + class Comments < Grape::API + before { authenticate! } + + resource :articles do + # Collect comments from articles + # + # Parameters: + # reference_id - comment id used as reference to collect comment + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected + # limit - amount of comments returned. The default value is 20 + # + # Example Request: + # GET /articles/12/comments?oldest&limit=10&reference_id=23 + get ":id/comments" do + + conditions = make_conditions_with_parameter(params) + article = find_article(environment.articles, params[:id]) + + if params[:reference_id] + comments = article.comments.send("#{params.key?(:oldest) ? 'older_than' : 'newer_than'}", params[:reference_id]).reorder("created_at DESC").find(:all, :conditions => conditions, :limit => limit) + else + comments = article.comments.reorder("created_at DESC").find(:all, :conditions => conditions, :limit => limit) + end + present comments, :with => Entities::Comment + + end + + get ":id/comments/:comment_id" do + article = find_article(environment.articles, params[:id]) + present article.comments.find(params[:comment_id]), :with => Entities::Comment + end + + # Example Request: + # POST api/v1/articles/12/comments?private_toke=234298743290432&body=new comment + post ":id/comments" do + article = find_article(environment.articles, params[:id]) + present article.comments.create(:author => current_person, :body => params[:body]), :with => Entities::Comment + end + end + + end + end + end +end diff --git a/lib/noosfero/api/v1/communities.rb b/lib/noosfero/api/v1/communities.rb new file mode 100644 index 0000000..69382da --- /dev/null +++ b/lib/noosfero/api/v1/communities.rb @@ -0,0 +1,72 @@ +module Noosfero + module API + module V1 + class Communities < Grape::API + before { authenticate! } + + resource :communities do + + # Collect comments from articles + # + # Parameters: + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected + # limit - amount of comments returned. The default value is 20 + # + # Example Request: + # GET /communities?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 + # GET /communities?reference_id=10&limit=10&oldest + get do + communities = select_filtered_collection_of(environment, 'communities', params) + communities = communities.visible_for_person(current_person) + present communities, :with => Entities::Community + end + + + # Example Request: + # POST api/v1/communties?private_token=234298743290432&community[name]=some_name + post do + params[:community] ||= {} + begin + community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment})) + rescue + community = Community.new(params[:community]) + end + + if !community.save + render_api_errors!(community.errors.full_messages) + end + + present community, :with => Entities::Community + end + + get ':id' do + community = environment.communities.visible.find_by_id(params[:id]) + present community, :with => Entities::Community + end + + end + + resource :people do + + segment '/:person_id' do + + resource :communities do + + get do + person = environment.people.find(params[:person_id]) + communities = select_filtered_collection_of(person, 'communities', params) + communities = communities.visible + present communities, :with => Entities::Community + end + + end + + end + + end + + end + end + end +end diff --git a/lib/noosfero/api/v1/enterprises.rb b/lib/noosfero/api/v1/enterprises.rb new file mode 100644 index 0000000..fb8ab13 --- /dev/null +++ b/lib/noosfero/api/v1/enterprises.rb @@ -0,0 +1,56 @@ +module Noosfero + module API + module V1 + class Enterprises < Grape::API + before { authenticate! } + + resource :enterprises do + + # Collect comments from articles + # + # Parameters: + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected + # limit - amount of comments returned. The default value is 20 + # + # Example Request: + # GET /enterprises?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 + # GET /enterprises?reference_id=10&limit=10&oldest + get do + enterprises = select_filtered_collection_of(environment, 'enterprises', params) + enterprises = enterprises.visible_for_person(current_person) + present enterprises, :with => Entities::Enterprise + end + + desc "Return one enterprise by id" + get ':id' do + enterprise = environment.enterprises.visible.find_by_id(params[:id]) + present enterprise, :with => Entities::Enterprise + end + + end + + resource :people do + + segment '/:person_id' do + + resource :enterprises do + + get do + person = environment.people.find(params[:person_id]) + enterprises = select_filtered_collection_of(person, 'enterprises', params) + enterprises = enterprises.visible + present enterprises, :with => Entities::Enterprise + end + + end + + end + + end + + + end + end + end +end diff --git a/lib/noosfero/api/v1/people.rb b/lib/noosfero/api/v1/people.rb new file mode 100644 index 0000000..0b69d4b --- /dev/null +++ b/lib/noosfero/api/v1/people.rb @@ -0,0 +1,42 @@ +module Noosfero + module API + module V1 + class People < Grape::API + before { authenticate! } + + resource :people do + + # Collect comments from articles + # + # Parameters: + # from - date where the search will begin. If nothing is passed the default date will be the date of the first article created + # oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected + # limit - amount of comments returned. The default value is 20 + # + # Example Request: + # GET /people?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10 + # GET /people?reference_id=10&limit=10&oldest + get do + people = select_filtered_collection_of(environment, 'people', params) + people = people.visible_for_person(current_person) + present people, :with => Entities::Person + end + + desc "Return the person information" + get ':id' do + person = environment.people.visible.find_by_id(params[:id]) + present person, :with => Entities::Person + end + + desc "Return the person friends" + get ':id/friends' do + friends = current_person.friends.visible + present friends, :with => Entities::Person + end + + end + + end + end + end +end diff --git a/lib/noosfero/api/v1/users.rb b/lib/noosfero/api/v1/users.rb new file mode 100644 index 0000000..f7491a5 --- /dev/null +++ b/lib/noosfero/api/v1/users.rb @@ -0,0 +1,48 @@ +module Noosfero + module API + module V1 + class Users < Grape::API + before { authenticate! } + + resource :users do + + #FIXME make the pagination + #FIXME put it on environment context + get do + present environment.users, :with => Entities::User + end + + # Example Request: + # POST api/v1/users?user[login]=some_login&user[password]=some + post do + user = User.new(params[:user]) + user.terms_of_use = environment.terms_of_use + user.environment = environment + if !user.save + render_api_errors!(user.errors.full_messages) + end + + present user, :with => Entities::User + end + + get ":id" do + present environment.users.find_by_id(params[:id]), :with => Entities::User + end + + get ":id/permissions" do + user = environment.users.find(params[:id]) + output = {} + user.person.role_assignments.map do |role_assigment| + if role_assigment.resource.respond_to?(:identifier) && role_assigment.resource.identifier == params[:profile] + output[:permissions] = role_assigment.role.permissions + end + end + present output + end + + end + + end + end + end +end diff --git a/test/unit/api/helpers_test.rb b/test/unit/api/helpers_test.rb index ff6d907..ab23150 100644 --- a/test/unit/api/helpers_test.rb +++ b/test/unit/api/helpers_test.rb @@ -1,8 +1,9 @@ require File.dirname(__FILE__) + '/test_helper' +require File.expand_path(File.dirname(__FILE__) + "/../../../lib/noosfero/api/helpers") class APITest < ActiveSupport::TestCase - include API::APIHelpers + include Noosfero::API::APIHelpers should 'get the current user with valid token' do user = create_user('someuser') diff --git a/test/unit/api/test_helper.rb b/test/unit/api/test_helper.rb index 4aa470d..ac4a670 100644 --- a/test/unit/api/test_helper.rb +++ b/test/unit/api/test_helper.rb @@ -5,7 +5,7 @@ class ActiveSupport::TestCase include Rack::Test::Methods def app - API::API + Noosfero::API::API end def login_api -- libgit2 0.21.2