From 739d2db0a9b57b30732abc593b80a1fef631c217 Mon Sep 17 00:00:00 2001 From: did Date: Mon, 23 Jan 2012 09:05:50 +0100 Subject: [PATCH 1/3] first api draft --- Gemfile.lock | 20 +++------ .../locomotive/api/base_controller.rb | 44 +++++++++++++++++++ .../locomotive/api/snippets_controller.rb | 28 ++++++++++++ .../locomotive/api/theme_assets_controller.rb | 25 +++++++++++ .../locomotive/api/tokens_controller.rb | 31 +++++++++++++ app/models/locomotive/account.rb | 44 +++++++++++++++++++ .../locomotive/theme_asset_presenter.rb | 4 +- config/routes.rb | 17 +++++++ doc/TODO | 18 ++++++-- doc/changelogs/version_2.txt | 1 - lib/locomotive/configuration.rb | 2 +- 11 files changed, 212 insertions(+), 22 deletions(-) create mode 100644 app/controllers/locomotive/api/base_controller.rb create mode 100644 app/controllers/locomotive/api/snippets_controller.rb create mode 100644 app/controllers/locomotive/api/theme_assets_controller.rb create mode 100644 app/controllers/locomotive/api/tokens_controller.rb diff --git a/Gemfile.lock b/Gemfile.lock index 14952577..53e6e2b7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,3 @@ -PATH - remote: ../gems/aloha-rails - specs: - locomotive-aloha-rails (0.20.1) - actionpack (~> 3.1.3) - PATH remote: ../gems/custom_fields specs: @@ -12,12 +6,6 @@ PATH carrierwave-mongoid (~> 0.1.3) mongoid (~> 2.4.0) -PATH - remote: ../gems/tinymce-rails - specs: - locomotive-tinymce-rails (3.4.7) - actionpack (~> 3.1.3) - GEM remote: http://rubygems.org/ specs: @@ -168,6 +156,10 @@ GEM kgio (2.7.2) launchy (2.0.5) addressable (~> 2.2.6) + locomotive-aloha-rails (0.20.1) + actionpack (~> 3.1.3) + locomotive-tinymce-rails (3.4.7) + actionpack (~> 3.1.3) locomotive_liquid (2.2.2) locomotive_mongoid_acts_as_tree (0.1.5.8) mail (2.3.0) @@ -319,8 +311,8 @@ DEPENDENCIES jquery-rails (~> 1.0.16) kaminari launchy - locomotive-aloha-rails! - locomotive-tinymce-rails! + locomotive-aloha-rails (~> 0.20.1) + locomotive-tinymce-rails (~> 3.4.7) locomotive_liquid (= 2.2.2) locomotive_mongoid_acts_as_tree (~> 0.1.5.8) mimetype-fu (~> 0.1.2) diff --git a/app/controllers/locomotive/api/base_controller.rb b/app/controllers/locomotive/api/base_controller.rb new file mode 100644 index 00000000..d0103bad --- /dev/null +++ b/app/controllers/locomotive/api/base_controller.rb @@ -0,0 +1,44 @@ +module Locomotive + module Api + class BaseController < ApplicationController + + include Locomotive::Routing::SiteDispatcher + include Locomotive::ActionController::LocaleHelpers + + before_filter :require_account + + before_filter :require_site + + # before_filter :validate_site_membership + + skip_before_filter :verify_authenticity_token + + self.responder = Locomotive::ActionController::Responder # custom responder + + respond_to :json, :xml + + rescue_from CanCan::AccessDenied do |exception| + ::Locomotive.log "[CanCan::AccessDenied] #{exception.inspect}" + + if request.xhr? + render :json => { :error => exception.message } + else + flash[:alert] = exception.message + + redirect_to pages_url + end + end + + protected + + def current_ability + @current_ability ||= Ability.new(current_locomotive_account, current_site) + end + + def require_account + authenticate_locomotive_account! + end + + end + end +end \ No newline at end of file diff --git a/app/controllers/locomotive/api/snippets_controller.rb b/app/controllers/locomotive/api/snippets_controller.rb new file mode 100644 index 00000000..ba2ab841 --- /dev/null +++ b/app/controllers/locomotive/api/snippets_controller.rb @@ -0,0 +1,28 @@ +module Locomotive + module Api + + class SnippetsController < BaseController + + include Locomotive::Routing::SiteDispatcher + + def index + @snippets = current_site.snippets.all + respond_with(@snippets) + end + + def create + @snippet = current_site.snippets.create(params[:snippet]) + respond_with @snippet, :location => edit_snippet_url(@snippet._id) + end + + def update + @snippet = current_site.snippets.find(params[:id]) + @snippet.update_attributes(params[:snippet]) + respond_with @snippet, :location => edit_snippet_url(@snippet._id) + end + + end + + end +end + diff --git a/app/controllers/locomotive/api/theme_assets_controller.rb b/app/controllers/locomotive/api/theme_assets_controller.rb new file mode 100644 index 00000000..a7509310 --- /dev/null +++ b/app/controllers/locomotive/api/theme_assets_controller.rb @@ -0,0 +1,25 @@ +module Locomotive + module Api + class ThemeAssetsController < BaseController + + include Locomotive::Routing::SiteDispatcher + + def index + @theme_assets = current_site.theme_assets.all + respond_with(@theme_assets) + end + + def create + @theme_asset = current_site.theme_assets.create(params[:theme_asset]) + respond_with @theme_asset, :location => edit_theme_asset_url(@theme_asset._id) + end + + def update + @theme_asset = current_site.theme_assets.find(params[:id]) + @theme_asset.update_attributes(params[:theme_asset]) + respond_with @theme_asset, :location => edit_theme_asset_url(@theme_asset._id) + end + + end + end +end diff --git a/app/controllers/locomotive/api/tokens_controller.rb b/app/controllers/locomotive/api/tokens_controller.rb new file mode 100644 index 00000000..501eb2aa --- /dev/null +++ b/app/controllers/locomotive/api/tokens_controller.rb @@ -0,0 +1,31 @@ +module Locomotive + module Api + class TokensController < BaseController + + skip_before_filter :require_account + + def create + begin + token = Account.create_api_token(current_site, params[:email], params[:password]) + respond_with({ :token => token }, :location => root_url) + rescue Exception => e + respond_with({ :message => e.message }, :status => 401, :location => root_url) + end + end + + def destroy + begin + token = Account.invalidate_api_token(params[:id]) + respond_with({ :token => token }, :location => root_url) + rescue Exception => e + respond_with({ :message => e.message }, :status => 404, :location => root_url) + end + end + + end + + end + +end + +# cAEERKkstnUya7UVxkqN \ No newline at end of file diff --git a/app/models/locomotive/account.rb b/app/models/locomotive/account.rb index 9ee38db2..fe7c0781 100644 --- a/app/models/locomotive/account.rb +++ b/app/models/locomotive/account.rb @@ -40,6 +40,50 @@ module Locomotive self.find_using_switch_site_token(token, age) || raise(::Mongoid::Errors::DocumentNotFound.new(self, token)) end + # Create the API token which will be passed to all the requests to the Locomotive API. + # It requires the credentials of an account with admin role. + # If an error occurs (invalid account, ...etc), this method raises an exception that has + # to be caught somewhere. + # + # @param [ Site ] site The site where the authentication request is made + # @param [ String ] email The email of the account + # @param [ String ] password The password of the account + # + # @return [ String ] The API token + # + def self.create_api_token(site, email, password) + raise 'The request must contain the user email and password.' if email.blank? or password.blank? + + account = self.where(:email => email.downcase).first + + raise 'Invalid email or password.' if account.nil? + + account.ensure_authentication_token! + + if not account.valid_password?(password) # TODO: check admin roles + raise 'Invalid email or password.' + end + + account.authentication_token + end + + # Logout the user responding to the token passed in parameter from the API. + # An exception is raised if no account corresponds to the token. + # + # @param [ String ] token The API token created by the create_api_token method. + # + # @return [ String ] The API token + # + def self.invalidate_api_token(token) + account = self.where(:authentication_token => token).first + + raise 'Invalid token.' if account.nil? + + account.reset_authentication_token! + + token + end + def devise_mailer Locomotive::DeviseMailer end diff --git a/app/presenters/locomotive/theme_asset_presenter.rb b/app/presenters/locomotive/theme_asset_presenter.rb index 8a3101bb..7f07c184 100644 --- a/app/presenters/locomotive/theme_asset_presenter.rb +++ b/app/presenters/locomotive/theme_asset_presenter.rb @@ -1,7 +1,7 @@ module Locomotive class ThemeAssetPresenter < BasePresenter - delegate :content_type, :to => :source + delegate :content_type, :folder, :to => :source def local_path self.source.local_path(true) @@ -24,7 +24,7 @@ module Locomotive end def included_methods - super + %w(content_type local_path url size dimensions updated_at) + super + %w(content_type folder local_path url size dimensions updated_at) end end diff --git a/config/routes.rb b/config/routes.rb index 5516e525..c1407810 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -52,9 +52,24 @@ Locomotive::Engine.routes.draw do # installation guide match '/installation' => 'installation#show', :defaults => { :step => 1 }, :as => :installation match '/installation/:step' => 'installation#show', :as => :installation_step + end Rails.application.routes.draw do + + # api + namespace :_locomotive, :module => 'locomotive' do + namespace :api do + + resources :tokens, :only => [:create, :destroy] + + resources :theme_assets + + resources :snippets + + end + end + # sitemap match '/sitemap.xml' => 'locomotive/public/sitemaps#show', :format => 'xml' @@ -73,4 +88,6 @@ Rails.application.routes.draw do match '/' => 'locomotive/public/pages#show' match '*path' => 'locomotive/public/pages#show' + + end \ No newline at end of file diff --git a/doc/TODO b/doc/TODO index 78b5a9cb..9202e6a8 100644 --- a/doc/TODO +++ b/doc/TODO @@ -83,13 +83,23 @@ x edit my site x remove sidebar - i18n - insert image -- deployment - - fix integration problems - - pre-compile assets +x deployment + x fix integration problems + x pre-compile assets +- API + - authentication from a token + controller to deliver a token + - api routes + - add a way to custom the as_json method within the presenters (by default as_json ?) + custom responder ? + - REST actions: + - CRUD assets + - CRUD pages + - CRUD snippets + - CRUD content types + - data ? - bugs: x unable to toggle the "required" check_boxes for content types - - unable to sign out + x unable to sign out - https://github.com/locomotivecms/engine/pull/281/files - disallow to click twice on the submit form button (spinner ?) diff --git a/doc/changelogs/version_2.txt b/doc/changelogs/version_2.txt index cce5767e..7dae9bb5 100644 --- a/doc/changelogs/version_2.txt +++ b/doc/changelogs/version_2.txt @@ -1,7 +1,6 @@ - rake locomotive:upgrade:rename_collections - locales updates (en / fr) - - theme_assets.images => theme_assets.image_picker - assets => content_assets - EditableXXX => Locomotive::EditableXXX (in mongodb) diff --git a/lib/locomotive/configuration.rb b/lib/locomotive/configuration.rb index 301c2cce..6a745d31 100644 --- a/lib/locomotive/configuration.rb +++ b/lib/locomotive/configuration.rb @@ -26,7 +26,7 @@ module Locomotive :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") }, - :devise_modules => [:rememberable, :database_authenticatable, :recoverable, :trackable, :validatable, :encryptable, { :encryptor => :sha1 }], + :devise_modules => [:rememberable, :database_authenticatable, :token_authenticatable, :recoverable, :trackable, :validatable, :encryptable, { :encryptor => :sha1 }], :context_assign_extensions => { } } From 0534c5504fdc3eb3a6e04da72a91a5136c9b49fa Mon Sep 17 00:00:00 2001 From: did Date: Tue, 24 Jan 2012 01:00:41 +0100 Subject: [PATCH 2/3] prevent pow to crash when no flash message (bug) + add api resources for pages --- app/presenters/locomotive/base_presenter.rb | 4 +++- config/routes.rb | 2 ++ lib/locomotive/action_controller/responder.rb | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/presenters/locomotive/base_presenter.rb b/app/presenters/locomotive/base_presenter.rb index f298cc88..925bb5ce 100644 --- a/app/presenters/locomotive/base_presenter.rb +++ b/app/presenters/locomotive/base_presenter.rb @@ -22,12 +22,14 @@ class Locomotive::BasePresenter self.source.persisted? || self.source.embedded? ? self.source._id.to_s : nil end + alias :_id :id + def ability? self.ability.present? end def included_methods - %w(id created_at updated_at) + %w(id _id created_at updated_at) end def as_json(methods = nil) diff --git a/config/routes.rb b/config/routes.rb index c1407810..87b2c53e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,6 +67,8 @@ Rails.application.routes.draw do resources :snippets + resources :pages + end end diff --git a/lib/locomotive/action_controller/responder.rb b/lib/locomotive/action_controller/responder.rb index 38b0dd10..a474751d 100644 --- a/lib/locomotive/action_controller/responder.rb +++ b/lib/locomotive/action_controller/responder.rb @@ -45,8 +45,10 @@ module Locomotive set_flash_message! message = controller.flash[type] - controller.headers['X-Message'] = message - controller.headers['X-Message-Type'] = type + unless message.blank? + controller.headers['X-Message'] = message + controller.headers['X-Message-Type'] = type + end yield if block_given? From ab507dc16583c047dbd47da75fd4d8769f8657ea Mon Sep 17 00:00:00 2001 From: did Date: Tue, 24 Jan 2012 21:16:53 +0100 Subject: [PATCH 3/3] change the default API base path + implement the API for content_assets, content_types and pages resources --- .../content_types/custom_fields_view.coffee | 8 ++++-- .../api/content_assets_controller.rb | 23 +++++++++++++++ .../api/content_types_controller.rb | 28 +++++++++++++++++++ .../locomotive/api/pages_controller.rb | 25 +++++++++++++++++ .../locomotive/api/snippets_controller.rb | 8 ++---- .../locomotive/api/theme_assets_controller.rb | 6 ++-- .../locomotive/api/tokens_controller.rb | 4 --- config/routes.rb | 11 ++++++-- doc/TODO | 25 +++++++++++------ 9 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 app/controllers/locomotive/api/content_assets_controller.rb create mode 100644 app/controllers/locomotive/api/content_types_controller.rb create mode 100644 app/controllers/locomotive/api/pages_controller.rb diff --git a/app/assets/javascripts/locomotive/views/content_types/custom_fields_view.coffee b/app/assets/javascripts/locomotive/views/content_types/custom_fields_view.coffee index 545985b3..8a73064f 100644 --- a/app/assets/javascripts/locomotive/views/content_types/custom_fields_view.coffee +++ b/app/assets/javascripts/locomotive/views/content_types/custom_fields_view.coffee @@ -66,12 +66,16 @@ class Locomotive.Views.ContentTypes.CustomFieldsView extends Backbone.View @add_entry(event) remove_entry: (custom_field, view) -> + if custom_field.isNew() + @model.get('entries_custom_fields').remove(custom_field) + else + custom_field.set _destroy: true + @_entry_views = _.reject @_entry_views, (_view) -> _view == view - @model.get('entries_custom_fields').remove(custom_field) @refresh_position_entries() - @$('> .empty').show() if @model.get('entries_custom_fields').length == 0 + @$('> .empty').show() if @_entry_views.length == 0 render_entries: -> if @model.get('entries_custom_fields').length == 0 diff --git a/app/controllers/locomotive/api/content_assets_controller.rb b/app/controllers/locomotive/api/content_assets_controller.rb new file mode 100644 index 00000000..00296527 --- /dev/null +++ b/app/controllers/locomotive/api/content_assets_controller.rb @@ -0,0 +1,23 @@ +module Locomotive + module Api + class ContentAssetsController < BaseController + + def index + @content_assets = current_site.content_assets + respond_with(@content_assets) + end + + def create + @content_asset = current_site.content_assets.create(params[:content_asset]) + respond_with @content_asset, :location => locomotive_api_content_asset_url(@content_asset._id) + end + + def update + @content_asset = current_site.content_assets.find(params[:id]) + @content_asset.update_attributes(params[:content_asset]) + respond_with @content_asset, :location => locomotive_api_content_asset_url(@content_asset._id) + end + + end + end +end diff --git a/app/controllers/locomotive/api/content_types_controller.rb b/app/controllers/locomotive/api/content_types_controller.rb new file mode 100644 index 00000000..a58838b4 --- /dev/null +++ b/app/controllers/locomotive/api/content_types_controller.rb @@ -0,0 +1,28 @@ +module Locomotive + module Api + class ContentTypesController < BaseController + + def index + @content_types = current_site.content_types + respond_with(@content_types) + end + + def create + @content_type = current_site.content_types.create(params[:content_type]) + respond_with @content_type, :location => edit_locomotive_api_content_type_url(@content_type._id) + end + + def edit + @content_type = current_site.content_types.find(params[:id]) + respond_with @content_type + end + + def update + @content_type = current_site.content_types.find(params[:id]) + @content_type.update_attributes(params[:content_type]) + respond_with @content_type, :location => edit_locomotive_api_content_type_url(@content_type._id) + end + + end + end +end diff --git a/app/controllers/locomotive/api/pages_controller.rb b/app/controllers/locomotive/api/pages_controller.rb new file mode 100644 index 00000000..dea546c4 --- /dev/null +++ b/app/controllers/locomotive/api/pages_controller.rb @@ -0,0 +1,25 @@ +module Locomotive + module Api + class PagesController < BaseController + + def index + @pages = current_site.pages.all + respond_with(@pages) + end + + def create + @page = current_site.pages.create(params[:page]) + respond_with @page, :location => edit_locomotive_api_page_url(@page._id) + end + + def update + @page = current_site.pages.find(params[:id]) + @page.update_attributes(params[:page]) + respond_with @page, :location => edit_locomotive_api_page_url(@page._id) + end + + end + + end +end + diff --git a/app/controllers/locomotive/api/snippets_controller.rb b/app/controllers/locomotive/api/snippets_controller.rb index ba2ab841..110ccf84 100644 --- a/app/controllers/locomotive/api/snippets_controller.rb +++ b/app/controllers/locomotive/api/snippets_controller.rb @@ -1,10 +1,7 @@ module Locomotive module Api - class SnippetsController < BaseController - include Locomotive::Routing::SiteDispatcher - def index @snippets = current_site.snippets.all respond_with(@snippets) @@ -12,17 +9,16 @@ module Locomotive def create @snippet = current_site.snippets.create(params[:snippet]) - respond_with @snippet, :location => edit_snippet_url(@snippet._id) + respond_with @snippet, :location => edit_locomotive_api_snippet_url(@snippet._id) end def update @snippet = current_site.snippets.find(params[:id]) @snippet.update_attributes(params[:snippet]) - respond_with @snippet, :location => edit_snippet_url(@snippet._id) + respond_with @snippet, :location => edit_locomotive_api_snippet_url(@snippet._id) end end - end end diff --git a/app/controllers/locomotive/api/theme_assets_controller.rb b/app/controllers/locomotive/api/theme_assets_controller.rb index a7509310..de8d29f6 100644 --- a/app/controllers/locomotive/api/theme_assets_controller.rb +++ b/app/controllers/locomotive/api/theme_assets_controller.rb @@ -2,8 +2,6 @@ module Locomotive module Api class ThemeAssetsController < BaseController - include Locomotive::Routing::SiteDispatcher - def index @theme_assets = current_site.theme_assets.all respond_with(@theme_assets) @@ -11,13 +9,13 @@ module Locomotive def create @theme_asset = current_site.theme_assets.create(params[:theme_asset]) - respond_with @theme_asset, :location => edit_theme_asset_url(@theme_asset._id) + respond_with @theme_asset, :location => edit_locomotive_api_theme_asset_url(@theme_asset._id) end def update @theme_asset = current_site.theme_assets.find(params[:id]) @theme_asset.update_attributes(params[:theme_asset]) - respond_with @theme_asset, :location => edit_theme_asset_url(@theme_asset._id) + respond_with @theme_asset, :location => edit_locomotive_api_theme_asset_url(@theme_asset._id) end end diff --git a/app/controllers/locomotive/api/tokens_controller.rb b/app/controllers/locomotive/api/tokens_controller.rb index 501eb2aa..637fd2d0 100644 --- a/app/controllers/locomotive/api/tokens_controller.rb +++ b/app/controllers/locomotive/api/tokens_controller.rb @@ -23,9 +23,5 @@ module Locomotive end end - end - end - -# cAEERKkstnUya7UVxkqN \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 87b2c53e..3e1b77cf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -41,6 +41,7 @@ Locomotive::Engine.routes.draw do put :sort, :on => :collection end + # TODO resources :custom_fields, :path => 'custom/:parent/:slug/fields' resources :cross_domain_sessions, :only => [:new, :create] @@ -57,18 +58,22 @@ end Rails.application.routes.draw do - # api - namespace :_locomotive, :module => 'locomotive' do - namespace :api do + # API + namespace :locomotive, :module => 'locomotive' do + namespace :api do resources :tokens, :only => [:create, :destroy] resources :theme_assets + resources :content_assets + resources :snippets resources :pages + resources :content_types + end end diff --git a/doc/TODO b/doc/TODO index 9202e6a8..31447fc0 100644 --- a/doc/TODO +++ b/doc/TODO @@ -87,20 +87,29 @@ x deployment x fix integration problems x pre-compile assets - API - - authentication from a token + controller to deliver a token - - api routes - - add a way to custom the as_json method within the presenters (by default as_json ?) + custom responder ? + x authentication from a token + controller to deliver a token + x api routes + x change api location + (- add a way to custom the as_json method within the presenters (by default as_json ?) + custom responder ?) - REST actions: - - CRUD assets - - CRUD pages - - CRUD snippets - - CRUD content types + x CRUD theme assets + x CRUD snippets + x CRUD content assets + x CRUD pages + x CRUD content types - data ? +- refactoring + - remove the import / export scripts + - remove the cross domain authentication (use auth_token instead) +- upgrade to rails 3.2 (https://github.com/locomotivecms/engine/pull/281/files) - bugs: x unable to toggle the "required" check_boxes for content types x unable to sign out - - https://github.com/locomotivecms/engine/pull/281/files + x unable to remove a field + + - "back to admin" link does not work if inline editor disabled + - disallow to click twice on the submit form button (spinner ?) - message to notify people if their browser is too old