spree_api 4.8.3 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v2/caching.rb +14 -8
  3. data/app/controllers/concerns/spree/api/v2/storefront/order_concern.rb +2 -2
  4. data/app/controllers/spree/api/v2/base_controller.rb +7 -1
  5. data/app/controllers/spree/api/v2/storefront/products_controller.rb +8 -1
  6. data/app/controllers/spree/api/v2/storefront/variants_controller.rb +41 -0
  7. data/app/controllers/spree/api/v2/storefront/wishlists_controller.rb +43 -0
  8. data/app/models/concerns/spree/webhooks/has_webhooks.rb +13 -5
  9. data/app/models/spree/oauth_application.rb +7 -0
  10. data/app/models/spree/order/webhooks.rb +39 -0
  11. data/app/models/spree/payment/webhooks.rb +23 -0
  12. data/app/models/spree/product/webhooks.rb +42 -0
  13. data/app/models/spree/shipment/webhooks.rb +19 -0
  14. data/app/models/spree/stock_item/webhooks.rb +40 -0
  15. data/app/models/spree/stock_movement/webhooks.rb +49 -0
  16. data/app/models/spree/variant/webhooks.rb +25 -0
  17. data/app/models/spree/webhooks/subscriber.rb +4 -0
  18. data/app/serializers/spree/api/v2/platform/admin_user_serializer.rb +11 -0
  19. data/app/serializers/spree/api/v2/platform/refund_serializer.rb +1 -0
  20. data/app/serializers/spree/api/v2/platform/store_credit_serializer.rb +1 -1
  21. data/app/serializers/spree/v2/storefront/shipping_category_serializer.rb +10 -0
  22. data/app/serializers/spree/v2/storefront/shipping_method_serializer.rb +10 -0
  23. data/app/services/spree/api/error_handler.rb +1 -0
  24. data/app/services/spree/webhooks/subscribers/handle_request.rb +4 -4
  25. data/app/services/spree/webhooks/subscribers/make_request.rb +2 -2
  26. data/app/services/spree/webhooks/subscribers/queue_requests.rb +3 -3
  27. data/config/initializers/doorkeeper.rb +2 -0
  28. data/config/routes.rb +4 -1
  29. data/lib/spree/api/configuration.rb +3 -1
  30. data/lib/spree/api/dependencies.rb +5 -1
  31. data/lib/spree/api/engine.rb +0 -8
  32. data/lib/spree/api/testing_support/matchers/webhooks.rb +4 -4
  33. data/lib/spree/api/testing_support/spree_webhooks.rb +2 -2
  34. metadata +17 -13
  35. data/app/models/spree/api/webhooks/order_decorator.rb +0 -43
  36. data/app/models/spree/api/webhooks/payment_decorator.rb +0 -26
  37. data/app/models/spree/api/webhooks/product_decorator.rb +0 -46
  38. data/app/models/spree/api/webhooks/shipment_decorator.rb +0 -21
  39. data/app/models/spree/api/webhooks/stock_item_decorator.rb +0 -43
  40. data/app/models/spree/api/webhooks/stock_movement_decorator.rb +0 -52
  41. data/app/models/spree/api/webhooks/variant_decorator.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 870a75c20df08101daae7df947e3dfd285a831eefe5e30105389771bcf5e578e
4
- data.tar.gz: edc2ee5df0c726087fa837456d38d271c27a35fa95762b1c2c422763fb132fd3
3
+ metadata.gz: 012d9b5279e5919cc152eba9678ca8682c1baca7b9256ff22c491db95d74de5a
4
+ data.tar.gz: 56e0fdb4db5ea020e7086b91fa3c401ea76527ea0b5408ca734e9a0abf72502b
5
5
  SHA512:
6
- metadata.gz: 414c7d4eeb5b12c336b63f8ce4f8a237a7bb38f372bef045926d201df72078bb0e17613496a6ef5e47c89554e95283f4d4f510f85e020f555a9b05763b3826e0
7
- data.tar.gz: 241adceec83317dda28aca5a2183c44d3906af1b197a3b0d41b0fd86fdd6ef88f7780135633b1e8da49824374752c28dcc9f354f5f05433c1ac7c2d01a3d3a67
6
+ metadata.gz: 8f9d2e9805651277ad363af26e8a97a10e89899336083e5b8f511d63bd99be127ab41b11c0c699fb42492c4d1f490a540273aaa6ec33ee9abcb221a36149b2e8
7
+ data.tar.gz: 8050de3c76e4d358a4d0198d87ea27e91ca2bc373ce8dfa61795207334b25e19751a7a1414bb17a3421c38037ae2ad51424e7a98506107c299f53acba8723ffa
@@ -5,22 +5,22 @@ module Spree
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  def collection_cache_key(collection)
8
- ids_and_timestamps = collection.unscope(:includes).unscope(:order).pluck(:id, :updated_at)
8
+ # Calling reselect('*') is a workaround for the joined translations table
9
+ collection_cache_key = collection.unscope(:includes).unscope(:order).unscope(:group).reselect('*').cache_key_with_version
9
10
 
10
- ids = ids_and_timestamps.map(&:first)
11
- max_updated_at = ids_and_timestamps.map(&:last).max
11
+ params.delete(:page) if params[:page] == 1
12
12
 
13
13
  cache_key_parts = [
14
- self.class.to_s,
15
- max_updated_at,
16
- ids,
14
+ collection_cache_key,
17
15
  resource_includes,
18
16
  sparse_fields,
19
- serializer_params,
17
+ serializer_params_cache_key,
20
18
  params[:sort]&.strip,
21
19
  params[:page]&.to_s&.strip,
22
20
  params[:per_page]&.to_s&.strip,
23
- ].flatten.join('-')
21
+ ]
22
+ cache_key_parts += additional_cache_key_parts if defined?(additional_cache_key_parts)
23
+ cache_key_parts = cache_key_parts.flatten.join('-')
24
24
 
25
25
  Digest::MD5.hexdigest(cache_key_parts)
26
26
  end
@@ -31,6 +31,12 @@ module Spree
31
31
  expires_in: Spree::Api::Config[:api_v2_collection_cache_ttl],
32
32
  }
33
33
  end
34
+
35
+ def serializer_params_cache_key
36
+ serializer_params.values.map do |param|
37
+ param.try(:cache_key) || param.try(:flatten).try(:join, '-') || param.try(:to_s)
38
+ end.compact.join('-')
39
+ end
34
40
  end
35
41
  end
36
42
  end
@@ -9,7 +9,7 @@ module Spree
9
9
  if result.success?
10
10
  render_serialized_payload { serialized_current_order }
11
11
  else
12
- render_error_payload(result.error)
12
+ render_error_payload(result.error&.value || result.value)
13
13
  end
14
14
  end
15
15
 
@@ -18,7 +18,7 @@ module Spree
18
18
  end
19
19
 
20
20
  def order_token
21
- request.headers['X-Spree-Order-Token'] || params[:order_token]
21
+ request.headers['X-Vendo-Order-Token'] || request.headers['X-Spree-Order-Token'] || params[:order_token]
22
22
  end
23
23
 
24
24
  def spree_current_order
@@ -142,11 +142,15 @@ module Spree
142
142
  }
143
143
  end
144
144
 
145
- def record_not_found
145
+ def record_not_found(exception)
146
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
147
+
146
148
  render_error_payload(I18n.t(:resource_not_found, scope: 'spree.api'), 404)
147
149
  end
148
150
 
149
151
  def access_denied(exception)
152
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
153
+
150
154
  render_error_payload(exception.message, 403)
151
155
  end
152
156
 
@@ -155,6 +159,8 @@ module Spree
155
159
  end
156
160
 
157
161
  def gateway_error(exception)
162
+ result = error_handler.call(exception: exception, opts: { user: spree_current_user })
163
+
158
164
  render_error_payload(exception.message)
159
165
  end
160
166
 
@@ -16,7 +16,14 @@ module Spree
16
16
  end
17
17
 
18
18
  def resource
19
- @resource ||= find_with_fallback_default_locale { scope.find_by(slug: params[:id]) } || scope.find(params[:id])
19
+ # fixed N+1 queries issue
20
+ variant_includes = [
21
+ :prices,
22
+ { option_values: [:option_type] }
23
+ ]
24
+
25
+ # using FriendlyId so old slugs still work
26
+ @resource ||= find_with_fallback_default_locale { scope.includes(variants: variant_includes, master: variant_includes).friendly.find(params[:id]) } || scope.includes(variants: variant_includes, master: variant_includes).friendly.find(params[:id])
20
27
  end
21
28
 
22
29
  def collection_sorter
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class VariantsController < ::Spree::Api::V2::ResourceController
6
+ private
7
+
8
+ def model_class
9
+ Spree::Variant
10
+ end
11
+
12
+ def scope
13
+ product_scope = Spree::Product.for_store(current_store)
14
+ product_scope = product_scope.accessible_by(current_ability, :show)
15
+ product_scope = product_scope.i18n if model_class.include?(TranslatableResource)
16
+
17
+ product = product_scope.friendly.find(params[:id])
18
+ product.variants_including_master
19
+ end
20
+
21
+ def collection_finder
22
+ Spree::Api::Dependencies.storefront_variant_finder.constantize
23
+ end
24
+
25
+ def collection_serializer
26
+ Spree::Api::Dependencies.storefront_variant_serializer.constantize
27
+ end
28
+
29
+ def scope_includes
30
+ variant_includes = {
31
+ prices: [],
32
+ option_values: :option_type,
33
+ }
34
+ variant_includes[:images] = [] if params[:include]&.match(/images/)
35
+ variant_includes
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -100,6 +100,41 @@ module Spree
100
100
  end
101
101
  end
102
102
 
103
+ def add_items
104
+ spree_authorize! :create, Spree::WishedItem
105
+
106
+ @wished_items = resource.wished_items
107
+ ApplicationRecord.transaction do
108
+ wished_items_attributes.each do |wished_item_attributes|
109
+ wished_item = @wished_items.find_by(variant_id: wished_item_attributes[:variant_id])
110
+
111
+ if wished_item
112
+ wished_item.update!(quantity: wished_item_attributes[:quantity] || 1)
113
+ else
114
+ Spree::WishedItem.create!(wished_item_attributes.merge(wishlist: resource))
115
+ end
116
+ end
117
+ end
118
+
119
+ resource.reload
120
+ render_serialized_payload { serialize_resource(resource) }
121
+ rescue ActiveRecord::RecordInvalid => e
122
+ render json: { error: e.record.errors.full_messages.to_sentence }, status: 422
123
+ end
124
+
125
+ def remove_items
126
+ spree_authorize! :destroy, Spree::WishedItem
127
+
128
+ ApplicationRecord.transaction do
129
+ resource.wished_items.where(id: wished_items_ids).each(&:destroy!)
130
+ end
131
+
132
+ resource.reload
133
+ render_serialized_payload { serialize_resource(resource) }
134
+ rescue ActiveRecord::RecordNotDestroyed => e
135
+ render json: { error: e.record.errors.full_messages.to_sentence }, status: 422
136
+ end
137
+
103
138
  private
104
139
 
105
140
  def scope(skip_cancancan: true)
@@ -164,6 +199,14 @@ module Spree
164
199
  1
165
200
  end
166
201
  end
202
+
203
+ def wished_items_attributes
204
+ params.permit(wished_items: permitted_wished_item_attributes).require(:wished_items)
205
+ end
206
+
207
+ def wished_items_ids
208
+ params.require(:wished_items_ids)
209
+ end
167
210
  end
168
211
  end
169
212
  end
@@ -10,13 +10,15 @@ module Spree
10
10
 
11
11
  def queue_webhooks_requests!(event_name)
12
12
  return if disable_spree_webhooks?
13
- return if Spree::Webhooks::Subscriber.none?
14
- return if Spree::Webhooks::Subscriber.active.with_urls_for(event_name).none?
13
+ return if no_webhooks_subscribers?(event_name)
15
14
  return if update_event?(event_name) && updating_only_ignored_attributes?
16
15
  return if webhook_payload_body.blank?
17
16
 
18
17
  Spree::Webhooks::Subscribers::QueueRequests.call(
19
- event_name: event_name, webhook_payload_body: webhook_payload_body, **webhooks_request_options
18
+ record: self,
19
+ event_name: event_name,
20
+ webhook_payload_body: webhook_payload_body,
21
+ **webhooks_request_options
20
22
  )
21
23
  end
22
24
 
@@ -43,7 +45,9 @@ module Spree
43
45
  end
44
46
 
45
47
  def included_relationships
46
- if resource_serializer.relationships_to_serialize
48
+ if self.class.respond_to?(:webhook_included_relationships)
49
+ self.class.webhook_included_relationships
50
+ elsif resource_serializer.relationships_to_serialize
47
51
  resource_serializer.relationships_to_serialize.keys
48
52
  else
49
53
  []
@@ -74,7 +78,11 @@ module Spree
74
78
  end
75
79
 
76
80
  def disable_spree_webhooks?
77
- ENV['DISABLE_SPREE_WEBHOOKS'] == 'true'
81
+ Spree::Webhooks.disabled?
82
+ end
83
+
84
+ def no_webhooks_subscribers?(event_name)
85
+ !Spree::Webhooks::Subscriber.active.with_urls_for(event_name).exists?
78
86
  end
79
87
 
80
88
  def webhooks_request_options
@@ -6,6 +6,13 @@ module Spree
6
6
 
7
7
  before_validation :set_blank_for_redirect_uri
8
8
 
9
+ # returns the last someone used this application
10
+ #
11
+ # @return [DateTime]
12
+ def last_used_at
13
+ access_tokens.order(:created_at).last&.created_at
14
+ end
15
+
9
16
  private
10
17
 
11
18
  def set_blank_for_redirect_uri
@@ -0,0 +1,39 @@
1
+ module Spree
2
+ class Order < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_order_resumed!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[order.canceled order.placed order.resumed order.shipped]
14
+ end
15
+ end
16
+
17
+ def send_order_canceled_webhook
18
+ queue_webhooks_requests!('order.canceled')
19
+ end
20
+
21
+ def send_order_placed_webhook
22
+ queue_webhooks_requests!('order.placed')
23
+ end
24
+
25
+ def send_order_resumed_webhook
26
+ queue_webhooks_requests!('order.resumed')
27
+ self.state_machine_resumed = false # to not fire the same webhook twice
28
+ end
29
+
30
+ def queue_webhooks_requests_for_order_resumed!
31
+ return if state_machine_resumed?
32
+ return unless state_previously_changed?
33
+ return unless state_previous_change&.last == 'resumed'
34
+
35
+ send_order_resumed_webhook
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Spree
2
+ class Payment < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ class_methods do
8
+ def custom_webhook_events
9
+ %w[payment.paid payment.voided]
10
+ end
11
+ end
12
+
13
+ def send_payment_voided_webhook
14
+ queue_webhooks_requests!('payment.voided')
15
+ end
16
+
17
+ def send_payment_completed_webhook
18
+ queue_webhooks_requests!('payment.paid')
19
+ order.queue_webhooks_requests!('order.paid') if order.paid?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ class Product < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_product_discontinued!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[product.back_in_stock product.backorderable product.discontinued
14
+ product.out_of_stock product.activated product.archived product.drafted]
15
+ end
16
+
17
+ def ignored_attributes_for_update_webhook_event
18
+ %w[status]
19
+ end
20
+ end
21
+
22
+ def send_product_activated_webhook
23
+ queue_webhooks_requests!('product.activated')
24
+ end
25
+
26
+ def send_product_archived_webhook
27
+ queue_webhooks_requests!('product.archived')
28
+ end
29
+
30
+ def send_product_drafted_webhook
31
+ queue_webhooks_requests!('product.drafted')
32
+ end
33
+
34
+ def queue_webhooks_requests_for_product_discontinued!
35
+ return unless discontinue_on_previously_changed?
36
+ return if (change = discontinue_on_previous_change).blank? || change.last.blank?
37
+
38
+ queue_webhooks_requests!('product.discontinued')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ class Shipment < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ class_methods do
8
+ def custom_webhook_events
9
+ %w[shipment.shipped]
10
+ end
11
+ end
12
+
13
+ def send_shipment_shipped_webhook
14
+ queue_webhooks_requests!('shipment.shipped')
15
+ order.queue_webhooks_requests!('order.shipped') if order.fully_shipped?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ class StockItem < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ around_save :queue_webhooks_requests_for_variant_backorderable!
9
+ around_save :queue_webhooks_requests_for_product_backorderable!
10
+ end
11
+
12
+ def queue_webhooks_requests_for_variant_backorderable!
13
+ was_out_of_stock = !variant.in_stock_or_backorderable?
14
+ was_not_backorderable = !variant_backorderable?
15
+ yield
16
+ if was_out_of_stock && was_not_backorderable && variant_backorderable?
17
+ reload
18
+ variant.queue_webhooks_requests!('variant.backorderable')
19
+ end
20
+ end
21
+
22
+ def queue_webhooks_requests_for_product_backorderable!
23
+ product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
24
+ product_was_not_backorderable = !product_backorderable?
25
+ yield
26
+ if product_was_out_of_stock && product_was_not_backorderable && product_backorderable?
27
+ variant.product.queue_webhooks_requests!('product.backorderable')
28
+ end
29
+ end
30
+
31
+ def product_backorderable?
32
+ Spree::StockItem.exists?(backorderable: true, variant_id: variant.product.variants.ids)
33
+ end
34
+
35
+ def variant_backorderable?
36
+ variant.stock_items.exists?(backorderable: true)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ module Spree
2
+ class StockMovement < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ around_save :queue_webhooks_requests_for_variant_out_of_stock!
9
+ around_save :queue_webhooks_requests_for_variant_back_in_stock!
10
+ around_save :queue_webhooks_requests_for_product_out_of_stock!
11
+ around_save :queue_webhooks_requests_for_product_back_in_stock!
12
+ end
13
+
14
+ def queue_webhooks_requests_for_variant_out_of_stock!
15
+ variant_in_stock_before_update = variant.in_stock_or_backorderable?
16
+ yield
17
+ if variant_in_stock_before_update && !variant.in_stock_or_backorderable?
18
+ reload
19
+ stock_item.variant.queue_webhooks_requests!('variant.out_of_stock')
20
+ end
21
+ end
22
+
23
+ def queue_webhooks_requests_for_variant_back_in_stock!
24
+ variant_was_out_of_stock = !variant.in_stock_or_backorderable?
25
+ yield
26
+ if variant_was_out_of_stock && variant.in_stock_or_backorderable?
27
+ reload
28
+ variant.queue_webhooks_requests!('variant.back_in_stock')
29
+ end
30
+ end
31
+
32
+ def queue_webhooks_requests_for_product_back_in_stock!
33
+ product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
34
+ yield
35
+ if product_was_out_of_stock && product.any_variant_in_stock_or_backorderable?
36
+ product.queue_webhooks_requests!('product.back_in_stock')
37
+ end
38
+ end
39
+
40
+ def queue_webhooks_requests_for_product_out_of_stock!
41
+ product_was_in_stock = product.any_variant_in_stock_or_backorderable?
42
+ yield
43
+ if product_was_in_stock && !product.any_variant_in_stock_or_backorderable?
44
+ product.queue_webhooks_requests!('product.out_of_stock')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ class Variant < Spree::Base
3
+ module Webhooks
4
+ extend ActiveSupport::Concern
5
+ include Spree::Webhooks::HasWebhooks
6
+
7
+ included do
8
+ after_update_commit :queue_webhooks_requests_for_variant_discontinued!
9
+ end
10
+
11
+ class_methods do
12
+ def custom_webhook_events
13
+ %w[variant.back_in_stock variant.backorderable variant.discontinued variant.out_of_stock]
14
+ end
15
+ end
16
+
17
+ def queue_webhooks_requests_for_variant_discontinued!
18
+ return unless discontinue_on_previously_changed?
19
+ return if (change = discontinue_on_previous_change).blank? || change.last.blank?
20
+
21
+ queue_webhooks_requests!('variant.discontinued')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -25,6 +25,10 @@ module Spree
25
25
 
26
26
  before_save :parse_subscriptions
27
27
 
28
+ def latest_event_at
29
+ events.order(:created_at).last&.created_at
30
+ end
31
+
28
32
  def self.with_urls_for(event)
29
33
  where(
30
34
  case ActiveRecord::Base.connection.adapter_name
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AdminUserSerializer < UserSerializer
6
+ set_type :admin_user
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -5,6 +5,7 @@ module Spree
5
5
  class RefundSerializer < BaseSerializer
6
6
  include ResourceSerializerConcern
7
7
 
8
+ belongs_to :refunder, serializer: Dependencies.platform_admin_user_serializer.constantize
8
9
  belongs_to :payment
9
10
  belongs_to :reimbursement
10
11
  belongs_to :refund_reason, object_method_name: :reason
@@ -6,7 +6,7 @@ module Spree
6
6
  include ResourceSerializerConcern
7
7
 
8
8
  belongs_to :user
9
- belongs_to :created_by, serializer: :user, type: :user
9
+ belongs_to :created_by, serializer: Dependencies.platform_admin_user_serializer.constantize
10
10
  belongs_to :store_credit_category, object_method_name: :category, id_method_name: :category_id
11
11
  belongs_to :store_credit_type, object_method_name: :credit_type, id_method_name: :type_id
12
12
 
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class ShippingCategorySerializer < BaseSerializer
5
+ set_type :shipping_category
6
+ attribute :name
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module V2
3
+ module Storefront
4
+ class ShippingMethodSerializer < BaseSerializer
5
+ set_type :shipping_method
6
+ attributes :name, :public_metadata
7
+ end
8
+ end
9
+ end
10
+ end
@@ -32,6 +32,7 @@ module Spree
32
32
  def report_error(exception:, message:, opts:)
33
33
  # overwrite this method in your application to support different error handlers
34
34
  # eg. Sentry, Rollbar, etc
35
+ Sentry.capture_exception(exception) if defined?(Sentry)
35
36
 
36
37
  success(exception: exception, message: message)
37
38
  end
@@ -19,7 +19,7 @@ module Spree
19
19
  end
20
20
  return process(:warn, msg("failed for '#{url}'")) if request.failed_request?
21
21
 
22
- process(:debug, msg("success for URL '#{url}'"))
22
+ process
23
23
  end
24
24
 
25
25
  private
@@ -30,8 +30,8 @@ module Spree
30
30
  delegate :id, :url, to: :subscriber
31
31
  delegate :created_at, :id, to: :event, prefix: true
32
32
 
33
- def process(log_level, msg)
34
- Rails.logger.public_send(log_level, msg)
33
+ def process(log_level = nil, msg = nil)
34
+ Rails.logger.public_send(log_level, msg) if msg.present? && log_level.present?
35
35
  make_request
36
36
  update_event(msg)
37
37
  nil
@@ -59,7 +59,7 @@ module Spree
59
59
  )
60
60
  end
61
61
 
62
- def update_event(msg)
62
+ def update_event(msg = nil)
63
63
  event.update(
64
64
  execution_time: execution_time,
65
65
  request_errors: msg,
@@ -45,7 +45,7 @@ module Spree
45
45
  def http
46
46
  http = Net::HTTP.new(uri_host, uri_port)
47
47
  http.read_timeout = webhooks_timeout.to_i if custom_read_timeout?
48
- http.use_ssl = true if use_ssl?
48
+ http.use_ssl = use_ssl?
49
49
  http
50
50
  end
51
51
 
@@ -71,7 +71,7 @@ module Spree
71
71
  end
72
72
 
73
73
  def use_ssl?
74
- !(Rails.env.development? || Rails.env.test?)
74
+ uri.scheme == 'https'
75
75
  end
76
76
 
77
77
  def uri
@@ -4,8 +4,8 @@ module Spree
4
4
  class QueueRequests
5
5
  prepend Spree::ServiceModule::Base
6
6
 
7
- def call(event_name:, webhook_payload_body:, **options)
8
- filtered_subscribers(event_name, webhook_payload_body, options).each do |subscriber|
7
+ def call(event_name:, webhook_payload_body:, record: nil, **options)
8
+ filtered_subscribers(event_name, webhook_payload_body, record, options).each do |subscriber|
9
9
  Spree::Webhooks::Subscribers::MakeRequestJob.perform_later(
10
10
  webhook_payload_body, event_name, subscriber
11
11
  )
@@ -14,7 +14,7 @@ module Spree
14
14
 
15
15
  private
16
16
 
17
- def filtered_subscribers(event_name, _, _)
17
+ def filtered_subscribers(event_name, webhook_payload_body, record, options)
18
18
  Spree::Webhooks::Subscriber.active.with_urls_for(event_name)
19
19
  end
20
20
  end
@@ -45,4 +45,6 @@ Doorkeeper.configure do
45
45
  # using Bcrupt for token secrets is currently not supported by Doorkeeper
46
46
  hash_token_secrets fallback: :plain
47
47
  hash_application_secrets fallback: :plain, using: '::Doorkeeper::SecretStoring::BCrypt'
48
+
49
+ access_token_expires_in 1.month
48
50
  end
data/config/routes.rb CHANGED
@@ -14,7 +14,7 @@ Spree::Core::Engine.add_routes do
14
14
  delete 'remove_line_item/:line_item_id', to: 'cart#remove_line_item', as: :cart_remove_line_item
15
15
  patch :set_quantity
16
16
  patch :apply_coupon_code
17
- delete 'remove_coupon_code/:coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code
17
+ delete 'remove_coupon_code/:coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code, constraints: { coupon_code: /[^\/]+/ }
18
18
  delete 'remove_coupon_code', to: 'cart#remove_coupon_code', as: :cart_remove_coupon_code_without_code
19
19
  get :estimate_shipping_rates
20
20
  patch :associate
@@ -45,6 +45,7 @@ Spree::Core::Engine.add_routes do
45
45
  get '/countries/:iso', to: 'countries#show', as: :country
46
46
  get '/order_status/:number', to: 'order_status#show', as: :order_status
47
47
  resources :products, only: %i[index show]
48
+ get '/products/:id/variants', to: 'variants#index', as: :product_variants
48
49
  resources :taxons, only: %i[index show], id: /.+/
49
50
  get '/stores/:code', to: 'stores#show', as: :store
50
51
  get '/store', to: 'stores#current', as: :current_store
@@ -59,6 +60,8 @@ Spree::Core::Engine.add_routes do
59
60
  post :add_item
60
61
  patch 'set_item_quantity/:item_id', to: 'wishlists#set_item_quantity', as: :set_item_quantity
61
62
  delete 'remove_item/:item_id', to: 'wishlists#remove_item', as: :remove_item
63
+ post :add_items
64
+ delete :remove_items
62
65
  end
63
66
  end
64
67
 
@@ -1,6 +1,8 @@
1
+ require 'spree/core/preferences/runtime_configuration'
2
+
1
3
  module Spree
2
4
  module Api
3
- class Configuration < Preferences::Configuration
5
+ class Configuration < Spree::Preferences::RuntimeConfiguration
4
6
  preference :api_v2_serializers_cache_ttl, :integer, default: 3600 # 1 hour in seconds
5
7
  preference :api_v2_collection_cache_ttl, :integer, default: 3600 # 1 hour in seconds
6
8
  preference :api_v2_collection_cache_namespace, :string, default: 'api_v2_collection_cache'
@@ -1,3 +1,5 @@
1
+ require 'spree/core/dependencies_helper'
2
+
1
3
  module Spree
2
4
  module Api
3
5
  class ApiDependencies
@@ -60,6 +62,7 @@ module Spree
60
62
  storefront_estimated_shipment_serializer: 'Spree::V2::Storefront::EstimatedShippingRateSerializer',
61
63
  storefront_store_serializer: 'Spree::V2::Storefront::StoreSerializer',
62
64
  storefront_order_serializer: 'Spree::V2::Storefront::OrderSerializer',
65
+ storefront_variant_serializer: 'Spree::V2::Storefront::VariantSerializer',
63
66
 
64
67
  # sorters
65
68
  storefront_collection_sorter: -> { Spree::Dependencies.collection_sorter },
@@ -81,11 +84,12 @@ module Spree
81
84
  storefront_find_by_variant_finder: -> { Spree::Dependencies.line_item_by_variant_finder },
82
85
  storefront_products_finder: -> { Spree::Dependencies.products_finder },
83
86
  storefront_taxon_finder: -> { Spree::Dependencies.taxon_finder },
87
+ storefront_variant_finder: -> { Spree::Dependencies.variant_finder },
84
88
 
85
89
  error_handler: 'Spree::Api::ErrorHandler',
86
90
 
87
91
  # serializers
88
- platform_admin_user_serializer: 'Spree::Api::V2::Platform::UserSerializer',
92
+ platform_admin_user_serializer: 'Spree::Api::V2::Platform::AdminUserSerializer',
89
93
 
90
94
  # coupon code handler
91
95
  platform_coupon_handler: -> { Spree::Dependencies.coupon_handler },
@@ -18,17 +18,9 @@ module Spree
18
18
  Migrations.new(config, engine_name).check
19
19
  end
20
20
 
21
- def self.activate
22
- Dir.glob(File.join(File.dirname(__FILE__), '../../../app/models/spree/api/webhooks/*_decorator*.rb')) do |c|
23
- Rails.application.config.cache_classes ? require(c) : load(c)
24
- end
25
- end
26
-
27
21
  def self.root
28
22
  @root ||= Pathname.new(File.expand_path('../../..', __dir__))
29
23
  end
30
-
31
- config.to_prepare &method(:activate).to_proc
32
24
  end
33
25
  end
34
26
  end
@@ -26,7 +26,7 @@
26
26
  # isn't added to the matcher definition, because it acts in a different
27
27
  # way depending on what's the resource being tested.
28
28
  #
29
- RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
29
+ RSpec::Matchers.define :emit_webhook_event do |event_to_emit, record = nil|
30
30
  match do |block|
31
31
  queue_requests = instance_double(Spree::Webhooks::Subscribers::QueueRequests)
32
32
 
@@ -36,7 +36,7 @@ RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
36
36
  with_webhooks_enabled { Timecop.freeze { block.call } }
37
37
 
38
38
  expect(queue_requests).to(
39
- have_received(:call).with(event_name: event_to_emit, webhook_payload_body: webhook_payload_body.to_json).once
39
+ have_received(:call).with(event_name: event_to_emit, webhook_payload_body: webhook_payload_body.to_json, record: record).once
40
40
  )
41
41
  end
42
42
 
@@ -61,7 +61,7 @@ RSpec::Matchers.define :emit_webhook_event do |event_to_emit|
61
61
  end
62
62
 
63
63
  def with_webhooks_enabled
64
- ENV['DISABLE_SPREE_WEBHOOKS'] = nil
64
+ Spree::Webhooks.disabled = false
65
65
  yield
66
- ENV['DISABLE_SPREE_WEBHOOKS'] = 'true'
66
+ Spree::Webhooks.disabled = true
67
67
  end
@@ -1,9 +1,9 @@
1
1
  RSpec.configure do |config|
2
2
  config.before(:each, :spree_webhooks) do
3
- ENV['DISABLE_SPREE_WEBHOOKS'] = nil
3
+ Spree::Webhooks.disabled = false
4
4
  end
5
5
 
6
6
  config.after(:each, :spree_webhooks) do
7
- ENV['DISABLE_SPREE_WEBHOOKS'] = 'true'
7
+ Spree::Webhooks.disabled = true
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.8.3
4
+ version: 4.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Bigg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n-tasks
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 4.8.3
117
+ version: 4.9.0
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - '='
123
123
  - !ruby/object:Gem::Version
124
- version: 4.8.3
124
+ version: 4.9.0
125
125
  description: Spree's API
126
126
  email:
127
127
  - ryan@spreecommerce.com
@@ -203,6 +203,7 @@ files:
203
203
  - app/controllers/spree/api/v2/storefront/products_controller.rb
204
204
  - app/controllers/spree/api/v2/storefront/stores_controller.rb
205
205
  - app/controllers/spree/api/v2/storefront/taxons_controller.rb
206
+ - app/controllers/spree/api/v2/storefront/variants_controller.rb
206
207
  - app/controllers/spree/api/v2/storefront/wishlists_controller.rb
207
208
  - app/helpers/spree/api/v2/collection_options_helpers.rb
208
209
  - app/helpers/spree/api/v2/display_money_helper.rb
@@ -211,16 +212,16 @@ files:
211
212
  - app/models/concerns/spree/user_api_authentication.rb
212
213
  - app/models/concerns/spree/user_api_methods.rb
213
214
  - app/models/concerns/spree/webhooks/has_webhooks.rb
214
- - app/models/spree/api/webhooks/order_decorator.rb
215
- - app/models/spree/api/webhooks/payment_decorator.rb
216
- - app/models/spree/api/webhooks/product_decorator.rb
217
- - app/models/spree/api/webhooks/shipment_decorator.rb
218
- - app/models/spree/api/webhooks/stock_item_decorator.rb
219
- - app/models/spree/api/webhooks/stock_movement_decorator.rb
220
- - app/models/spree/api/webhooks/variant_decorator.rb
221
215
  - app/models/spree/oauth_access_grant.rb
222
216
  - app/models/spree/oauth_access_token.rb
223
217
  - app/models/spree/oauth_application.rb
218
+ - app/models/spree/order/webhooks.rb
219
+ - app/models/spree/payment/webhooks.rb
220
+ - app/models/spree/product/webhooks.rb
221
+ - app/models/spree/shipment/webhooks.rb
222
+ - app/models/spree/stock_item/webhooks.rb
223
+ - app/models/spree/stock_movement/webhooks.rb
224
+ - app/models/spree/variant/webhooks.rb
224
225
  - app/models/spree/webhooks/base.rb
225
226
  - app/models/spree/webhooks/event.rb
226
227
  - app/models/spree/webhooks/event_signature.rb
@@ -232,6 +233,7 @@ files:
232
233
  - app/serializers/spree/api/v2/base_serializer.rb
233
234
  - app/serializers/spree/api/v2/platform/address_serializer.rb
234
235
  - app/serializers/spree/api/v2/platform/adjustment_serializer.rb
236
+ - app/serializers/spree/api/v2/platform/admin_user_serializer.rb
235
237
  - app/serializers/spree/api/v2/platform/asset_serializer.rb
236
238
  - app/serializers/spree/api/v2/platform/base_serializer.rb
237
239
  - app/serializers/spree/api/v2/platform/calculator_serializer.rb
@@ -337,6 +339,8 @@ files:
337
339
  - app/serializers/spree/v2/storefront/product_serializer.rb
338
340
  - app/serializers/spree/v2/storefront/promotion_serializer.rb
339
341
  - app/serializers/spree/v2/storefront/shipment_serializer.rb
342
+ - app/serializers/spree/v2/storefront/shipping_category_serializer.rb
343
+ - app/serializers/spree/v2/storefront/shipping_method_serializer.rb
340
344
  - app/serializers/spree/v2/storefront/shipping_rate_serializer.rb
341
345
  - app/serializers/spree/v2/storefront/state_serializer.rb
342
346
  - app/serializers/spree/v2/storefront/stock_location_serializer.rb
@@ -402,9 +406,9 @@ licenses:
402
406
  - BSD-3-Clause
403
407
  metadata:
404
408
  bug_tracker_uri: https://github.com/spree/spree/issues
405
- changelog_uri: https://github.com/spree/spree/releases/tag/v4.8.3
409
+ changelog_uri: https://github.com/spree/spree/releases/tag/v4.9.0
406
410
  documentation_uri: https://docs.spreecommerce.org/
407
- source_code_uri: https://github.com/spree/spree/tree/v4.8.3
411
+ source_code_uri: https://github.com/spree/spree/tree/v4.9.0
408
412
  post_install_message:
409
413
  rdoc_options: []
410
414
  require_paths:
@@ -1,43 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module OrderDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[order.canceled order.placed order.resumed order.shipped]
8
- end
9
-
10
- base.after_update_commit :queue_webhooks_requests_for_order_resumed!
11
- end
12
-
13
- def after_cancel
14
- super
15
- queue_webhooks_requests!('order.canceled')
16
- end
17
-
18
- def finalize!
19
- super
20
- queue_webhooks_requests!('order.placed')
21
- end
22
-
23
- def after_resume
24
- super
25
- queue_webhooks_requests!('order.resumed')
26
- self.state_machine_resumed = false
27
- end
28
-
29
- private
30
-
31
- def queue_webhooks_requests_for_order_resumed!
32
- return if state_machine_resumed?
33
- return unless state_previously_changed?
34
- return unless state_previous_change&.last == 'resumed'
35
-
36
- queue_webhooks_requests!('order.resumed')
37
- end
38
- end
39
- end
40
- end
41
- end
42
-
43
- Spree::Order.prepend(Spree::Api::Webhooks::OrderDecorator)
@@ -1,26 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module PaymentDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[payment.paid payment.voided]
8
- end
9
- end
10
-
11
- def after_void
12
- super
13
- queue_webhooks_requests!('payment.voided')
14
- end
15
-
16
- def after_completed
17
- super
18
- queue_webhooks_requests!('payment.paid')
19
- order.queue_webhooks_requests!('order.paid') if order.paid?
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Payment.prepend(Spree::Api::Webhooks::PaymentDecorator)
@@ -1,46 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module ProductDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[product.back_in_stock product.backorderable product.discontinued
8
- product.out_of_stock product.activated product.archived product.drafted]
9
- end
10
-
11
- def base.ignored_attributes_for_update_webhook_event
12
- %w[status]
13
- end
14
-
15
- base.after_update_commit :queue_webhooks_requests_for_product_discontinued!
16
- end
17
-
18
- def after_activate
19
- super
20
- queue_webhooks_requests!('product.activated')
21
- end
22
-
23
- def after_archive
24
- super
25
- queue_webhooks_requests!('product.archived')
26
- end
27
-
28
- def after_draft
29
- super
30
- queue_webhooks_requests!('product.drafted')
31
- end
32
-
33
- private
34
-
35
- def queue_webhooks_requests_for_product_discontinued!
36
- return unless discontinue_on_previously_changed?
37
- return if (change = discontinue_on_previous_change).blank? || change.last.blank?
38
-
39
- queue_webhooks_requests!('product.discontinued')
40
- end
41
- end
42
- end
43
- end
44
- end
45
-
46
- Spree::Product.prepend(Spree::Api::Webhooks::ProductDecorator)
@@ -1,21 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module ShipmentDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[shipment.shipped]
8
- end
9
- end
10
-
11
- def after_ship
12
- super
13
- queue_webhooks_requests!('shipment.shipped')
14
- order.queue_webhooks_requests!('order.shipped') if order.fully_shipped?
15
- end
16
- end
17
- end
18
- end
19
- end
20
-
21
- Spree::Shipment.prepend(Spree::Api::Webhooks::ShipmentDecorator)
@@ -1,43 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module StockItemDecorator
5
- def self.prepended(base)
6
- base.around_save :queue_webhooks_requests_for_variant_backorderable!
7
- base.around_save :queue_webhooks_requests_for_product_backorderable!
8
- end
9
-
10
- private
11
-
12
- def queue_webhooks_requests_for_variant_backorderable!
13
- was_out_of_stock = !variant.in_stock_or_backorderable?
14
- was_not_backorderable = !variant_backorderable?
15
- yield
16
- if was_out_of_stock && was_not_backorderable && variant_backorderable?
17
- reload
18
- variant.queue_webhooks_requests!('variant.backorderable')
19
- end
20
- end
21
-
22
- def queue_webhooks_requests_for_product_backorderable!
23
- product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
24
- product_was_not_backorderable = !product_backorderable?
25
- yield
26
- if product_was_out_of_stock && product_was_not_backorderable && product_backorderable?
27
- variant.product.queue_webhooks_requests!('product.backorderable')
28
- end
29
- end
30
-
31
- def product_backorderable?
32
- Spree::StockItem.exists?(backorderable: true, variant_id: variant.product.variants.ids)
33
- end
34
-
35
- def variant_backorderable?
36
- variant.stock_items.exists?(backorderable: true)
37
- end
38
- end
39
- end
40
- end
41
- end
42
-
43
- Spree::StockItem.prepend(Spree::Api::Webhooks::StockItemDecorator)
@@ -1,52 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module StockMovementDecorator
5
- def self.prepended(base)
6
- base.around_save :queue_webhooks_requests_for_variant_out_of_stock!
7
- base.around_save :queue_webhooks_requests_for_variant_back_in_stock!
8
- base.around_save :queue_webhooks_requests_for_product_out_of_stock!
9
- base.around_save :queue_webhooks_requests_for_product_back_in_stock!
10
- end
11
-
12
- private
13
-
14
- def queue_webhooks_requests_for_variant_out_of_stock!
15
- variant_in_stock_before_update = variant.in_stock_or_backorderable?
16
- yield
17
- if variant_in_stock_before_update && !variant.in_stock_or_backorderable?
18
- reload
19
- stock_item.variant.queue_webhooks_requests!('variant.out_of_stock')
20
- end
21
- end
22
-
23
- def queue_webhooks_requests_for_variant_back_in_stock!
24
- variant_was_out_of_stock = !variant.in_stock_or_backorderable?
25
- yield
26
- if variant_was_out_of_stock && variant.in_stock_or_backorderable?
27
- reload
28
- variant.queue_webhooks_requests!('variant.back_in_stock')
29
- end
30
- end
31
-
32
- def queue_webhooks_requests_for_product_back_in_stock!
33
- product_was_out_of_stock = !product.any_variant_in_stock_or_backorderable?
34
- yield
35
- if product_was_out_of_stock && product.any_variant_in_stock_or_backorderable?
36
- product.queue_webhooks_requests!('product.back_in_stock')
37
- end
38
- end
39
-
40
- def queue_webhooks_requests_for_product_out_of_stock!
41
- product_was_in_stock = product.any_variant_in_stock_or_backorderable?
42
- yield
43
- if product_was_in_stock && !product.any_variant_in_stock_or_backorderable?
44
- product.queue_webhooks_requests!('product.out_of_stock')
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end
51
-
52
- Spree::StockMovement.prepend(Spree::Api::Webhooks::StockMovementDecorator)
@@ -1,26 +0,0 @@
1
- module Spree
2
- module Api
3
- module Webhooks
4
- module VariantDecorator
5
- def self.prepended(base)
6
- def base.custom_webhook_events
7
- %w[variant.back_in_stock variant.backorderable variant.discontinued variant.out_of_stock]
8
- end
9
-
10
- base.after_update_commit :queue_webhooks_requests_for_variant_discontinued!
11
- end
12
-
13
- private
14
-
15
- def queue_webhooks_requests_for_variant_discontinued!
16
- return unless discontinue_on_previously_changed?
17
- return if (change = discontinue_on_previous_change).blank? || change.last.blank?
18
-
19
- queue_webhooks_requests!('variant.discontinued')
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Spree::Variant.prepend(Spree::Api::Webhooks::VariantDecorator)