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/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/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 new file mode 100644 index 00000000..110ccf84 --- /dev/null +++ b/app/controllers/locomotive/api/snippets_controller.rb @@ -0,0 +1,24 @@ +module Locomotive + module Api + class SnippetsController < BaseController + + 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_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_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 new file mode 100644 index 00000000..de8d29f6 --- /dev/null +++ b/app/controllers/locomotive/api/theme_assets_controller.rb @@ -0,0 +1,23 @@ +module Locomotive + module Api + class ThemeAssetsController < BaseController + + 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_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_locomotive_api_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..637fd2d0 --- /dev/null +++ b/app/controllers/locomotive/api/tokens_controller.rb @@ -0,0 +1,27 @@ +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 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/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/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..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] @@ -52,9 +53,30 @@ 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 :content_assets + + resources :snippets + + resources :pages + + resources :content_types + + end + end + # sitemap match '/sitemap.xml' => 'locomotive/public/sitemaps#show', :format => 'xml' @@ -73,4 +95,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..31447fc0 100644 --- a/doc/TODO +++ b/doc/TODO @@ -83,14 +83,33 @@ 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 + 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: + 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 - - unable to sign out - - https://github.com/locomotivecms/engine/pull/281/files + x unable to sign out + 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 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/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? diff --git a/lib/locomotive/configuration.rb b/lib/locomotive/configuration.rb index 5040675b..b2b271cd 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 => { } }