spree_api 5.5.2 → 5.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d3239fe21ccaa81a45ba98e56cf9729a2a1e19717753acd2a2bc7942486ed65
4
- data.tar.gz: 145a65f51b1cf39e0a0e8ed74e1c2a964abce29f2eac0bc6403894bb85ccbde2
3
+ metadata.gz: 25e42383bfb6759f7fd2f6831ab3d168ccfc049bae4a9c01e9dc8c63bb4a6bfc
4
+ data.tar.gz: a982087e479d23574fea03267796a054d79536fb7dc2fb7aa090452e7aab4f6e
5
5
  SHA512:
6
- metadata.gz: f361c60828f3dc8d9e2d67191333ca91947ba5d8cc7c11cb3c93ca691c7cb151cd7db27d9b9fbadea1fcec190709de9de4b61a569bba0c00a9173037659bceba
7
- data.tar.gz: 17634d38ca453b81aeac073b9cb77e81d6ed70b621e1f9aadd161060373cf0a70b44a10d91f910e0a5ffc0f28a93e83214e18bfcfaacd57572d702ed3b7d0c2d
6
+ metadata.gz: 7ee5ac82a202296ec6c83bf2d0a37b201f7905f6b7fa9caca1de5f8c4874717e44495cc37ef15bf5e545ae30a8464dccc8d9c70603836ab8a50e5752551e70f3
7
+ data.tar.gz: fa9e45fa9b4b269fb11143db92afe6786cfaa369d172ad8a7aae4e860f3efa48d5cde09785bc916cfb1fcfbed4eba38d1d5c74caa1cd7ff8cc962c2743dba367
@@ -83,10 +83,15 @@ module Spree
83
83
  end
84
84
 
85
85
  # PATCH /api/v3/store/carts/:id/associate
86
- # Associates a guest cart with the currently authenticated user
87
- # Requires: JWT authentication + cart ID in URL
86
+ # Associates a guest cart with the currently authenticated user.
87
+ # Requires: JWT authentication + cart ID in URL + possession of the
88
+ # cart's token (x-spree-token). The token is the credential that
89
+ # authorizes claiming the cart, so it is required even when the cart
90
+ # is already the caller's own.
88
91
  def associate
89
92
  @cart = find_cart_for_association
93
+ authorize!(:update, @cart, cart_token)
94
+ require_cart_token!
90
95
 
91
96
  result = Spree.cart_associate_service.call(guest_order: @cart, user: current_user, guest_only: true)
92
97
 
@@ -173,6 +178,14 @@ module Spree
173
178
  def find_cart_for_association
174
179
  current_store.carts.where(user: [nil, current_user]).find_by_prefix_id!(params[:id])
175
180
  end
181
+
182
+ # Claiming a cart requires presenting its token, even when the caller
183
+ # already owns it — the token, not ownership, is the claim credential.
184
+ def require_cart_token!
185
+ valid = cart_token.present? && cart_token == @cart.token
186
+
187
+ raise CanCan::AccessDenied.new(nil, :update, @cart) unless valid
188
+ end
176
189
  end
177
190
  end
178
191
  end
@@ -14,17 +14,18 @@ module Spree
14
14
  filters: (search_filters || {}).merge('_category' => category)
15
15
  )
16
16
 
17
- {
18
- filters: result.filters,
19
- sort_options: result.sort_options,
20
- default_sort: result.default_sort,
21
- total_count: result.total_count
22
- }
17
+ serialize_resource(result)
23
18
  end
24
19
 
25
20
  render json: json
26
21
  end
27
22
 
23
+ protected
24
+
25
+ def serializer_class
26
+ Spree.api.product_filters_serializer
27
+ end
28
+
28
29
  private
29
30
 
30
31
  def filters_cache_key
@@ -13,10 +13,15 @@ module Spree
13
13
 
14
14
  # Accept optional second argument for backward compatibility with jobs
15
15
  # enqueued before this change was deployed.
16
- def perform(delivery_id, _deprecated_secret_key = nil)
16
+ #
17
+ # `payload_secrets` carries credentials withheld from the persisted payload
18
+ # so the outgoing request body stays complete. See
19
+ # {Spree::WebhookPayloadRedaction}.
20
+ def perform(delivery_id, _deprecated_secret_key = nil, payload_secrets: nil)
17
21
  delivery = Spree::WebhookDelivery.find_by(id: delivery_id)
18
22
  return if delivery.nil?
19
23
 
24
+ delivery.payload_secrets = payload_secrets
20
25
  secret_key = delivery.webhook_endpoint.secret_key
21
26
  Spree::Webhooks::DeliverWebhook.call(delivery: delivery, secret_key: secret_key)
22
27
  end
@@ -25,10 +25,15 @@ module Spree
25
25
 
26
26
  attributes :event_name, :event_id, :response_code, :execution_time,
27
27
  :error_type, :request_errors, :response_body, :success,
28
- :payload,
29
28
  created_at: :iso8601, updated_at: :iso8601,
30
29
  delivered_at: :iso8601
31
30
 
31
+ # Redacted again on read: deliveries written before payload redaction
32
+ # shipped still hold live credentials in the column.
33
+ attribute :payload do |delivery|
34
+ Spree::WebhookPayloadRedaction.split(delivery.payload).first
35
+ end
36
+
32
37
  attribute :webhook_endpoint_id do |delivery|
33
38
  delivery.webhook_endpoint&.prefixed_id
34
39
  end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterAvailabilityOptionSerializer < BaseSerializer
5
+ typelize id: [:string, enum: %w[in_stock out_of_stock]], count: :number
6
+
7
+ attributes :id, :count
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterAvailabilitySerializer < BaseSerializer
5
+ typelize id: :string,
6
+ type: "'availability'",
7
+ options: [:ProductFilterAvailabilityOption, multi: true]
8
+
9
+ attributes :id, :type
10
+
11
+ many :options, resource: proc { Spree.api.product_filter_availability_option_serializer }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterCategoryOptionSerializer < BaseSerializer
5
+ typelize id: :string,
6
+ name: :string,
7
+ permalink: :string,
8
+ count: :number
9
+
10
+ attributes :id, :name, :permalink, :count
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterCategorySerializer < BaseSerializer
5
+ typelize id: :string,
6
+ type: "'category'",
7
+ options: [:ProductFilterCategoryOption, multi: true]
8
+
9
+ attributes :id, :type
10
+
11
+ many :options, resource: proc { Spree.api.product_filter_category_option_serializer }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterOptionSerializer < BaseSerializer
5
+ typelize id: :string,
6
+ type: "'option'",
7
+ name: :string,
8
+ label: :string,
9
+ kind: :string,
10
+ options: [:ProductFilterOptionValue, multi: true]
11
+
12
+ attributes :id, :type, :name, :label, :kind
13
+
14
+ many :options, resource: proc { Spree.api.product_filter_option_value_serializer }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterOptionValueSerializer < BaseSerializer
5
+ typelize id: :string,
6
+ name: :string,
7
+ label: :string,
8
+ position: :number,
9
+ color_code: [:string, nullable: true],
10
+ image_url: [:string, nullable: true],
11
+ count: :number
12
+
13
+ attributes :id, :name, :label, :position, :color_code, :image_url, :count
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterPriceRangeSerializer < BaseSerializer
5
+ typelize id: :string,
6
+ type: "'price_range'",
7
+ min: :number,
8
+ max: :number,
9
+ currency: :string
10
+
11
+ attributes :id, :type, :min, :max, :currency
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFilterSortOptionSerializer < BaseSerializer
5
+ typelize id: :string
6
+
7
+ attributes :id
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductFiltersSerializer < BaseSerializer
5
+ typelize default_sort: :string,
6
+ total_count: :number,
7
+ filters: 'Array<ProductFilterPriceRange | ProductFilterAvailability | ProductFilterOption | ProductFilterCategory>',
8
+ sort_options: [:ProductFilterSortOption, { multi: true }]
9
+
10
+ attributes :default_sort, :total_count
11
+
12
+ attribute :filters do |result|
13
+ result.filters.filter_map do |filter|
14
+ filter_type = filter[:type]
15
+ serializer_class = case filter_type
16
+ when 'price_range'
17
+ Spree.api.product_filter_price_range_serializer
18
+ when 'availability'
19
+ Spree.api.product_filter_availability_serializer
20
+ when 'option'
21
+ Spree.api.product_filter_option_serializer
22
+ when 'category'
23
+ Spree.api.product_filter_category_serializer
24
+ else
25
+ raise ArgumentError, "Unknown filter type: #{filter_type.inspect}"
26
+ end
27
+
28
+ serializer_class.new(filter, params: params).serializable_hash
29
+ end
30
+ end
31
+
32
+ many :sort_options, resource: proc { Spree.api.product_filter_sort_option_serializer }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -56,7 +56,7 @@ module Spree
56
56
  'X-Spree-Webhook-Timestamp' => webhook_timestamp.to_s,
57
57
  'X-Spree-Webhook-Event' => @delivery.event_name
58
58
  }
59
- body = @delivery.payload.to_json
59
+ body = payload_json
60
60
  http_options = { open_timeout: TIMEOUT, read_timeout: TIMEOUT, verify_mode: ssl_verify_mode }
61
61
 
62
62
  # SSRF protection is disabled in development so webhooks can reach
@@ -77,10 +77,14 @@ module Spree
77
77
  end
78
78
 
79
79
  def generate_signature
80
- payload_json = @delivery.payload.to_json
81
80
  OpenSSL::HMAC.hexdigest('SHA256', @secret_key, "#{webhook_timestamp}.#{payload_json}")
82
81
  end
83
82
 
83
+ # Memoized so the signature is computed over exactly the bytes sent.
84
+ def payload_json
85
+ @payload_json ||= @delivery.deliverable_payload.to_json
86
+ end
87
+
84
88
  def webhook_timestamp
85
89
  @webhook_timestamp ||= Time.current.to_i
86
90
  end
@@ -50,13 +50,16 @@ module Spree
50
50
  )
51
51
  end
52
52
 
53
+ # Live credentials go over the wire but never into the delivery log.
54
+ persisted_payload, secrets = Spree::WebhookPayloadRedaction.split(payload)
55
+
53
56
  delivery = endpoint.webhook_deliveries.create!(
54
57
  event_name: event.name,
55
58
  event_id: event.id,
56
- payload: payload
59
+ payload: persisted_payload
57
60
  )
58
61
 
59
- Spree::WebhookDeliveryJob.perform_later(delivery.id)
62
+ Spree::WebhookDeliveryJob.perform_later(delivery.id, payload_secrets: secrets)
60
63
  rescue ActiveRecord::RecordNotUnique
61
64
  # Race condition: another thread already created this delivery — safe to ignore
62
65
  rescue StandardError => e
@@ -136,6 +136,15 @@ module Spree
136
136
  custom_field_serializer: 'Spree::Api::V3::CustomFieldSerializer',
137
137
  shipping_category_serializer: 'Spree::Api::V3::ShippingCategorySerializer',
138
138
  tax_category_serializer: 'Spree::Api::V3::TaxCategorySerializer',
139
+ product_filters_serializer: 'Spree::Api::V3::ProductFiltersSerializer',
140
+ product_filter_price_range_serializer: 'Spree::Api::V3::ProductFilterPriceRangeSerializer',
141
+ product_filter_availability_serializer: 'Spree::Api::V3::ProductFilterAvailabilitySerializer',
142
+ product_filter_availability_option_serializer: 'Spree::Api::V3::ProductFilterAvailabilityOptionSerializer',
143
+ product_filter_option_serializer: 'Spree::Api::V3::ProductFilterOptionSerializer',
144
+ product_filter_option_value_serializer: 'Spree::Api::V3::ProductFilterOptionValueSerializer',
145
+ product_filter_category_serializer: 'Spree::Api::V3::ProductFilterCategorySerializer',
146
+ product_filter_category_option_serializer: 'Spree::Api::V3::ProductFilterCategoryOptionSerializer',
147
+ product_filter_sort_option_serializer: 'Spree::Api::V3::ProductFilterSortOptionSerializer',
139
148
 
140
149
  # v3 event serializers (for models without Store API endpoints yet)
141
150
  asset_serializer: 'Spree::Api::V3::AssetSerializer',
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: 5.5.2
4
+ version: 5.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rswag-specs
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 5.5.2
75
+ version: 5.5.4
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 5.5.2
82
+ version: 5.5.4
83
83
  description: Spree's API
84
84
  email:
85
85
  - hello@spreecommerce.org
@@ -305,6 +305,15 @@ files:
305
305
  - app/serializers/spree/api/v3/policy_serializer.rb
306
306
  - app/serializers/spree/api/v3/price_history_serializer.rb
307
307
  - app/serializers/spree/api/v3/price_serializer.rb
308
+ - app/serializers/spree/api/v3/product_filter_availability_option_serializer.rb
309
+ - app/serializers/spree/api/v3/product_filter_availability_serializer.rb
310
+ - app/serializers/spree/api/v3/product_filter_category_option_serializer.rb
311
+ - app/serializers/spree/api/v3/product_filter_category_serializer.rb
312
+ - app/serializers/spree/api/v3/product_filter_option_serializer.rb
313
+ - app/serializers/spree/api/v3/product_filter_option_value_serializer.rb
314
+ - app/serializers/spree/api/v3/product_filter_price_range_serializer.rb
315
+ - app/serializers/spree/api/v3/product_filter_sort_option_serializer.rb
316
+ - app/serializers/spree/api/v3/product_filters_serializer.rb
308
317
  - app/serializers/spree/api/v3/product_publication_serializer.rb
309
318
  - app/serializers/spree/api/v3/product_serializer.rb
310
319
  - app/serializers/spree/api/v3/promotion_serializer.rb
@@ -355,9 +364,9 @@ licenses:
355
364
  - BSD-3-Clause
356
365
  metadata:
357
366
  bug_tracker_uri: https://github.com/spree/spree/issues
358
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.2
367
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.4
359
368
  documentation_uri: https://docs.spreecommerce.org/
360
- source_code_uri: https://github.com/spree/spree/tree/v5.5.2
369
+ source_code_uri: https://github.com/spree/spree/tree/v5.5.4
361
370
  post_install_message:
362
371
  rdoc_options: []
363
372
  require_paths: