refinerycms-api 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rbenv-gemsets +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +57 -0
  7. data/LICENSE +27 -0
  8. data/Rakefile +21 -0
  9. data/app/controllers/refinery/api/base_controller.rb +174 -0
  10. data/app/controllers/refinery/api/v1/blog/posts_controller.rb +78 -0
  11. data/app/controllers/refinery/api/v1/images_controller.rb +76 -0
  12. data/app/controllers/refinery/api/v1/inquiries/inquiries_controller.rb +69 -0
  13. data/app/controllers/refinery/api/v1/pages_controller.rb +77 -0
  14. data/app/controllers/refinery/api/v1/resources_controller.rb +66 -0
  15. data/app/decorators/models/refinery/authentication/devise/user_decorator.rb +5 -0
  16. data/app/helpers/refinery/api/api_helpers.rb +54 -0
  17. data/app/models/concerns/refinery/user_api_authentication.rb +19 -0
  18. data/app/models/refinery/ability.rb +59 -0
  19. data/app/views/refinery/api/errors/gateway_error.v1.rabl +2 -0
  20. data/app/views/refinery/api/errors/invalid_api_key.v1.rabl +2 -0
  21. data/app/views/refinery/api/errors/invalid_resource.v1.rabl +3 -0
  22. data/app/views/refinery/api/errors/must_specify_api_key.v1.rabl +2 -0
  23. data/app/views/refinery/api/errors/not_found.v1.rabl +2 -0
  24. data/app/views/refinery/api/errors/unauthorized.v1.rabl +2 -0
  25. data/app/views/refinery/api/v1/blog/posts/index.v1.rabl +5 -0
  26. data/app/views/refinery/api/v1/blog/posts/new.v1.rabl +3 -0
  27. data/app/views/refinery/api/v1/blog/posts/show.v1.rabl +5 -0
  28. data/app/views/refinery/api/v1/images/index.v1.rabl +5 -0
  29. data/app/views/refinery/api/v1/images/new.v1.rabl +3 -0
  30. data/app/views/refinery/api/v1/images/show.v1.rabl +6 -0
  31. data/app/views/refinery/api/v1/inquiries/inquiries/index.v1.rabl +5 -0
  32. data/app/views/refinery/api/v1/inquiries/inquiries/new.v1.rabl +3 -0
  33. data/app/views/refinery/api/v1/inquiries/inquiries/show.v1.rabl +5 -0
  34. data/app/views/refinery/api/v1/pages/index.v1.rabl +8 -0
  35. data/app/views/refinery/api/v1/pages/new.v1.rabl +3 -0
  36. data/app/views/refinery/api/v1/pages/pages.v1.rabl +5 -0
  37. data/app/views/refinery/api/v1/pages/show.v1.rabl +9 -0
  38. data/app/views/refinery/api/v1/resources/index.v1.rabl +5 -0
  39. data/app/views/refinery/api/v1/resources/new.v1.rabl +3 -0
  40. data/app/views/refinery/api/v1/resources/show.v1.rabl +6 -0
  41. data/bin/rails +5 -0
  42. data/bin/rake +21 -0
  43. data/bin/rspec +22 -0
  44. data/bin/spring +18 -0
  45. data/config/initializers/metal_load_paths.rb +1 -0
  46. data/config/locales/en.yml +27 -0
  47. data/config/routes.rb +24 -0
  48. data/db/migrate/20160501141738_add_api_key_to_refinery_authentication_devise_users.rb +8 -0
  49. data/lib/generators/refinery/api/api_generator.rb +16 -0
  50. data/lib/generators/refinery/api/templates/config/initializers/refinery/api.rb.erb +7 -0
  51. data/lib/refinery/api.rb +29 -0
  52. data/lib/refinery/api/configuration.rb +32 -0
  53. data/lib/refinery/api/controller_helpers/auth.rb +76 -0
  54. data/lib/refinery/api/controller_helpers/strong_parameters.rb +37 -0
  55. data/lib/refinery/api/controller_setup.rb +20 -0
  56. data/lib/refinery/api/engine.rb +52 -0
  57. data/lib/refinery/api/responders.rb +11 -0
  58. data/lib/refinery/api/responders/rabl_template.rb +30 -0
  59. data/lib/refinery/api/testing_support/caching.rb +10 -0
  60. data/lib/refinery/api/testing_support/helpers.rb +44 -0
  61. data/lib/refinery/api/testing_support/setup.rb +16 -0
  62. data/lib/refinery/permitted_attributes.rb +40 -0
  63. data/lib/refinery/responder.rb +45 -0
  64. data/lib/refinerycms-api.rb +3 -0
  65. data/readme.md +69 -0
  66. data/refinerycms_api.gemspec +22 -0
  67. data/script/rails +9 -0
  68. data/spec/controllers/refinery/api/base_controller_spec.rb +73 -0
  69. data/spec/controllers/refinery/api/v1/blog/posts_controller_spec.rb +140 -0
  70. data/spec/controllers/refinery/api/v1/images_controller_spec.rb +93 -0
  71. data/spec/controllers/refinery/api/v1/inquiries/inquiries_controller_spec.rb +126 -0
  72. data/spec/controllers/refinery/api/v1/pages_controller_spec.rb +150 -0
  73. data/spec/controllers/refinery/api/v1/resources_controller_spec.rb +94 -0
  74. data/spec/fixtures/refinery_is_awesome.txt +1 -0
  75. data/spec/fixtures/thinking-cat.jpg +0 -0
  76. data/spec/models/refinery/user_spec.rb +23 -0
  77. data/spec/requests/rabl_cache_spec.rb +17 -0
  78. data/spec/requests/ransackable_attributes_spec.rb +80 -0
  79. data/spec/requests/version_spec.rb +23 -0
  80. data/spec/shared_examples/protect_product_actions.rb +17 -0
  81. data/spec/spec_helper.rb +77 -0
  82. data/spec/support/controller_hacks.rb +33 -0
  83. data/spec/support/database_cleaner.rb +14 -0
  84. data/spec/support/have_attributes_matcher.rb +9 -0
  85. data/tasks/refinery_api.rake +14 -0
  86. data/tasks/rspec.rake +4 -0
  87. metadata +240 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fea548db2f08cdcac03566e7f651fbdd8251c017
4
+ data.tar.gz: ea1815014f6903f77300cf2e726655a4aa4152c9
5
+ SHA512:
6
+ metadata.gz: 636256ea9c45a20c3bb6c43f42e93f9db932135bb32ba1026c5724fe778e0494b003f0d7f97f8abf48055345a74dbb04ebb7c987a5c106afe947bcad007128a8
7
+ data.tar.gz: f3cf461aa73fa93a41c023913ce9c103ed7b7b811908a356b5e9d6375ed3a858d0496b5fe5ba9713b10530243d67a5520fd21435346d552b3e63356d39d23cd5
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .DS_Store
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .gems
20
+
21
+ spec/dummy
@@ -0,0 +1 @@
1
+ .gems
@@ -0,0 +1 @@
1
+ 2.2.1
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ cache: bundler
3
+ bundler_args: --without development
4
+ before_script: "bin/rake refinery:testing:dummy_app"
5
+ env:
6
+ - DB=postgresql
7
+ - DB=mysql
8
+ rvm:
9
+ - 2.3.0
10
+ - 2.2
11
+ - 2.1
12
+ sudo: false
data/Gemfile ADDED
@@ -0,0 +1,57 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "refinerycms-authentication-devise", '~> 1.0.4'
4
+ gem "refinerycms-blog", github: 'refinery/refinerycms-blog', branch: 'bugfix/decoupling'
5
+ gem "refinerycms-inquiries", github: 'refinery/refinerycms-inquiries', branch: 'master'
6
+
7
+ gemspec
8
+
9
+ git "https://github.com/refinery/refinerycms", branch: "master" do
10
+ gem "refinerycms"
11
+
12
+ group :test do
13
+ gem "refinerycms-testing"
14
+ end
15
+ end
16
+
17
+
18
+
19
+ # Database Configuration
20
+ unless ENV["TRAVIS"]
21
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
22
+ gem "sqlite3", :platform => :ruby
23
+ end
24
+
25
+ if !ENV["TRAVIS"] || ENV["DB"] == "mysql"
26
+ gem "activerecord-jdbcmysql-adapter", :platform => :jruby
27
+ gem "jdbc-mysql", "= 5.1.13", :platform => :jruby
28
+ gem "mysql2", :platform => :ruby
29
+ end
30
+
31
+ if !ENV["TRAVIS"] || ENV["DB"] == "postgresql"
32
+ gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
33
+ gem "pg", :platform => :ruby
34
+ end
35
+
36
+ gem "jruby-openssl", :platform => :jruby
37
+
38
+ # Refinery/rails should pull in the proper versions of these
39
+ group :assets do
40
+ gem "sass-rails"
41
+ gem "coffee-rails"
42
+ gem "uglifier"
43
+ end
44
+
45
+ group :development do
46
+ gem 'quiet_assets'
47
+ end
48
+
49
+ group :test do
50
+ gem "launchy"
51
+ gem 'rspec-activemodel-mocks', '~> 1.0.2'
52
+ end
53
+
54
+ # Load local gems according to Refinery developer preference.
55
+ if File.exist? local_gemfile = File.expand_path("../.gemfile", __FILE__)
56
+ eval File.read(local_gemfile)
57
+ end
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2007-2015, Spree Commerce, Inc. and other contributors
2
+ Copyright (c) 2016, Brice Sanchez and other contributors
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+ * Neither the name Spree nor the names of its contributors may be used to
14
+ endorse or promote products derived from this software without specific
15
+ prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ ENGINE_PATH = File.dirname(__FILE__)
9
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
10
+
11
+ if File.exists?(APP_RAKEFILE)
12
+ load 'rails/tasks/engine.rake'
13
+ end
14
+
15
+ require "refinerycms-testing"
16
+ Refinery::Testing::Railtie.load_dummy_tasks(ENGINE_PATH)
17
+
18
+ load File.expand_path('../tasks/rspec.rake', __FILE__)
19
+ load File.expand_path('../tasks/refinery_api.rake', __FILE__)
20
+
21
+ task :default => :spec
@@ -0,0 +1,174 @@
1
+ require_dependency 'refinery/api/controller_setup'
2
+
3
+ module Refinery
4
+ module Api
5
+ class BaseController < ActionController::Base
6
+ include Refinery::Api::ControllerSetup
7
+ include Refinery::Api::ControllerHelpers::StrongParameters
8
+
9
+ attr_accessor :current_api_user
10
+
11
+ before_action :set_content_type
12
+ before_action :load_user
13
+ before_action :authorize_for_order, if: Proc.new { order_token.present? }
14
+ before_action :authenticate_user
15
+ before_action :load_user_roles
16
+
17
+ rescue_from ActionController::ParameterMissing, with: :error_during_processing
18
+ rescue_from ActiveRecord::RecordInvalid, with: :error_during_processing
19
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
20
+ rescue_from CanCan::AccessDenied, with: :unauthorized
21
+ rescue_from Refinery::Api::GatewayError, with: :gateway_error
22
+
23
+ helper Refinery::Api::ApiHelpers
24
+
25
+ def map_nested_attributes_keys(klass, attributes)
26
+ nested_keys = klass.nested_attributes_options.keys
27
+ attributes.inject({}) do |h, (k,v)|
28
+ key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
29
+ h[key] = v
30
+ h
31
+ end.with_indifferent_access
32
+ end
33
+
34
+ # users should be able to set price when importing orders via api
35
+ def permitted_line_item_attributes
36
+ if @current_user_roles.include?("admin")
37
+ super + [:price, :variant_id, :sku]
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def content_type
44
+ case params[:format]
45
+ when "json"
46
+ "application/json; charset=utf-8"
47
+ when "xml"
48
+ "text/xml; charset=utf-8"
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ def authorisation_manager
55
+ @authorisation_manager ||= ::Refinery::Core::AuthorisationManager.new
56
+ end
57
+ # We ❤ you, too ️
58
+ alias_method :authorization_manager, :authorisation_manager
59
+
60
+ private
61
+
62
+ def set_content_type
63
+ headers["Content-Type"] = content_type
64
+ end
65
+
66
+ def load_user
67
+ @current_api_user = Refinery::Api.user_class.find_by(refinery_api_key: api_key.to_s)
68
+ end
69
+
70
+ def authenticate_user
71
+ return if @current_api_user
72
+
73
+ if requires_authentication? && api_key.blank? && order_token.blank?
74
+ render "refinery/api/errors/must_specify_api_key", status: 401 and return
75
+ elsif order_token.blank? && (requires_authentication? || api_key.present?)
76
+ render "refinery/api/errors/invalid_api_key", status: 401 and return
77
+ else
78
+ # An anonymous user
79
+ @current_api_user = Refinery::Api.user_class.new
80
+ end
81
+ end
82
+
83
+ def load_user_roles
84
+ @current_user_roles = @current_api_user ? @current_api_user.roles.pluck(:title) : []
85
+ end
86
+
87
+ def unauthorized
88
+ render "refinery/api/errors/unauthorized", status: 401 and return
89
+ end
90
+
91
+ def error_during_processing(exception)
92
+ Rails.logger.error exception.message
93
+ Rails.logger.error exception.backtrace.join("\n")
94
+
95
+ unprocessable_entity(exception.message)
96
+ end
97
+
98
+ def unprocessable_entity(message)
99
+ render text: { exception: message }.to_json, status: 422
100
+ end
101
+
102
+ def gateway_error(exception)
103
+ @order.errors.add(:base, exception.message)
104
+ invalid_resource!(@order)
105
+ end
106
+
107
+ def requires_authentication?
108
+ Refinery::Api.requires_authentication
109
+ end
110
+
111
+ def not_found
112
+ render "refinery/api/errors/not_found", status: 404 and return
113
+ end
114
+
115
+ def current_ability
116
+ Refinery::Ability.new(current_api_user)
117
+ end
118
+
119
+ def invalid_resource!(resource)
120
+ @resource = resource
121
+ render "refinery/api/errors/invalid_resource", status: 422
122
+ end
123
+
124
+ def api_key
125
+ request.headers["X-Refinery-Token"] || params[:token]
126
+ end
127
+ helper_method :api_key
128
+
129
+ def order_token
130
+ request.headers["X-Refinery-Order-Token"] || params[:order_token]
131
+ end
132
+
133
+ def find_product(id)
134
+ product_scope.friendly.find(id.to_s)
135
+ rescue ActiveRecord::RecordNotFound
136
+ product_scope.find(id)
137
+ end
138
+
139
+ def product_scope
140
+ if @current_user_roles.include?("admin")
141
+ scope = Product.with_deleted.accessible_by(current_ability, :read).includes(*product_includes)
142
+
143
+ unless params[:show_deleted]
144
+ scope = scope.not_deleted
145
+ end
146
+ unless params[:show_discontinued]
147
+ scope = scope.not_discontinued
148
+ end
149
+ else
150
+ scope = Product.accessible_by(current_ability, :read).active.includes(*product_includes)
151
+ end
152
+
153
+ scope
154
+ end
155
+
156
+ def variants_associations
157
+ [{ option_values: :option_type }, :default_price, :images]
158
+ end
159
+
160
+ def product_includes
161
+ [:option_types, :taxons, product_properties: :property, variants: variants_associations, master: variants_associations]
162
+ end
163
+
164
+ def order_id
165
+ params[:order_id] || params[:checkout_id] || params[:order_number]
166
+ end
167
+
168
+ def authorize_for_order
169
+ @order = Refinery::Order.find_by(number: order_id)
170
+ authorize! :read, @order, order_token
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,78 @@
1
+ if defined?(Refinery::Blog)
2
+ module Refinery
3
+ module Api
4
+ module V1
5
+ module Blog
6
+ class PostsController < Refinery::Api::BaseController
7
+
8
+ def index
9
+ if params[:ids]
10
+ @posts = Refinery::Blog::Post.
11
+ accessible_by(current_ability, :read).
12
+ where(id: params[:ids].split(','))
13
+ else
14
+ @posts = Refinery::Blog::Post.
15
+ accessible_by(current_ability, :read).
16
+ # ransack(params[:q]).result
17
+ newest_first
18
+ end
19
+
20
+ respond_with(@posts)
21
+ end
22
+
23
+ def show
24
+ @post = post
25
+ respond_with(@post)
26
+ end
27
+
28
+ def new
29
+ end
30
+
31
+ def create
32
+ authorize! :create, ::Refinery::Blog::Post
33
+ @post = Refinery::Blog::Post.new(post_params)
34
+
35
+ if @post.save
36
+ respond_with(@post, status: 201, default_template: :show)
37
+ else
38
+ invalid_resource!(@post)
39
+ end
40
+ end
41
+
42
+ def update
43
+ authorize! :update, post
44
+ if post.update_attributes(post_params)
45
+ respond_with(post, status: 200, default_template: :show)
46
+ else
47
+ invalid_resource!(post)
48
+ end
49
+ end
50
+
51
+ def destroy
52
+ authorize! :destroy, post
53
+ post.destroy
54
+ respond_with(post, status: 204)
55
+ end
56
+
57
+ private
58
+
59
+ def post
60
+ @post ||= Refinery::Blog::Post.
61
+ accessible_by(current_ability, :read).
62
+ find(params[:id])
63
+ end
64
+
65
+ def post_params
66
+ if params[:post] && !params[:post].empty?
67
+ params.require(:post).permit(permitted_blog_post_attributes)
68
+ else
69
+ {}
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,76 @@
1
+ module Refinery
2
+ module Api
3
+ module V1
4
+ class ImagesController < Refinery::Api::BaseController
5
+ def index
6
+ @images = Refinery::Image.
7
+ includes(:translations).
8
+ accessible_by(current_ability, :read)
9
+ respond_with(@images)
10
+ end
11
+
12
+ def show
13
+ @image = Refinery::Image.
14
+ includes(:translations).
15
+ accessible_by(current_ability, :read).
16
+ find(params[:id])
17
+ respond_with(@image)
18
+ end
19
+
20
+ def new
21
+ end
22
+
23
+ def create
24
+ authorize! :create, Image
25
+ @images = []
26
+ begin
27
+ if params[:image].present? && params[:image][:image].is_a?(Array)
28
+ params[:image][:image].each do |image|
29
+ params[:image][:image_title] = params[:image][:image_title].presence || auto_title(image.original_filename)
30
+ @images << (@image = ::Refinery::Image.create({image: image}.merge(image_params.except(:image))))
31
+ end
32
+ else
33
+ @images << (@image = ::Refinery::Image.create(image_params))
34
+ end
35
+ rescue NotImplementedError
36
+ logger.warn($!.message)
37
+ @image = ::Refinery::Image.new
38
+ end
39
+
40
+ if @images.all?(&:valid?)
41
+ respond_with(@images, status: 201, default_template: :show)
42
+ else
43
+ invalid_resource!(@images)
44
+ end
45
+ end
46
+
47
+ def update
48
+ @image = Refinery::Image.accessible_by(current_ability, :update).find(params[:id])
49
+ if @image.update_attributes(image_params)
50
+ respond_with(@image, default_template: :show)
51
+ else
52
+ invalid_resource!(@image)
53
+ end
54
+ end
55
+
56
+ def destroy
57
+ @image = Refinery::Image.accessible_by(current_ability, :destroy).find(params[:id])
58
+ @image.destroy
59
+ respond_with(@image, status: 204)
60
+ end
61
+
62
+ protected
63
+
64
+ def auto_title(filename)
65
+ CGI::unescape(filename.to_s).gsub(/\.\w+$/, '').titleize
66
+ end
67
+
68
+ private
69
+
70
+ def image_params
71
+ params.require(:image).permit(permitted_image_attributes)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end