spree_api 5.4.0.rc3 → 5.4.0.rc4.1

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/spree/api/v3/store/data_feeds_controller.rb +20 -0
  3. data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +1 -3
  4. data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
  5. data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +19 -0
  6. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +2 -1
  7. data/app/serializers/spree/api/v3/admin/delivery_rate_serializer.rb +2 -0
  8. data/app/serializers/spree/api/v3/admin/digital_link_serializer.rb +1 -0
  9. data/app/serializers/spree/api/v3/admin/discount_serializer.rb +1 -0
  10. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -1
  11. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +2 -0
  12. data/app/serializers/spree/api/v3/admin/media_serializer.rb +2 -0
  13. data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +2 -0
  14. data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +2 -0
  15. data/app/serializers/spree/api/v3/admin/order_serializer.rb +4 -3
  16. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -1
  17. data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +1 -0
  18. data/app/serializers/spree/api/v3/admin/product_serializer.rb +5 -3
  19. data/app/serializers/spree/api/v3/admin/refund_serializer.rb +2 -0
  20. data/app/serializers/spree/api/v3/admin/reimbursement_serializer.rb +2 -0
  21. data/app/serializers/spree/api/v3/admin/return_authorization_serializer.rb +2 -0
  22. data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +2 -0
  23. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +2 -0
  24. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +5 -7
  25. data/app/serializers/spree/api/v3/cart_serializer.rb +1 -2
  26. data/app/serializers/spree/api/v3/category_serializer.rb +4 -5
  27. data/app/serializers/spree/api/v3/{metafield_serializer.rb → custom_field_serializer.rb} +3 -3
  28. data/app/serializers/spree/api/v3/customer_serializer.rb +1 -2
  29. data/app/serializers/spree/api/v3/digital_link_serializer.rb +1 -2
  30. data/app/serializers/spree/api/v3/fulfillment_serializer.rb +1 -2
  31. data/app/serializers/spree/api/v3/gift_card_serializer.rb +0 -1
  32. data/app/serializers/spree/api/v3/line_item_serializer.rb +1 -2
  33. data/app/serializers/spree/api/v3/media_serializer.rb +1 -2
  34. data/app/serializers/spree/api/v3/order_serializer.rb +1 -1
  35. data/app/serializers/spree/api/v3/payment_serializer.rb +1 -2
  36. data/app/serializers/spree/api/v3/payment_session_serializer.rb +1 -1
  37. data/app/serializers/spree/api/v3/payment_setup_session_serializer.rb +1 -2
  38. data/app/serializers/spree/api/v3/policy_serializer.rb +0 -1
  39. data/app/serializers/spree/api/v3/product_serializer.rb +4 -4
  40. data/app/serializers/spree/api/v3/refund_serializer.rb +1 -1
  41. data/app/serializers/spree/api/v3/reimbursement_serializer.rb +1 -2
  42. data/app/serializers/spree/api/v3/report_serializer.rb +1 -2
  43. data/app/serializers/spree/api/v3/return_authorization_serializer.rb +1 -1
  44. data/app/serializers/spree/api/v3/stock_item_serializer.rb +1 -2
  45. data/app/serializers/spree/api/v3/variant_serializer.rb +4 -5
  46. data/app/serializers/spree/api/v3/wishlist_item_serializer.rb +1 -2
  47. data/app/serializers/spree/api/v3/wishlist_serializer.rb +1 -2
  48. data/app/services/spree/api/v3/filters_aggregator.rb +46 -11
  49. data/app/services/spree/webhooks/deliver_webhook.rb +25 -16
  50. data/app/subscribers/spree/webhook_event_subscriber.rb +18 -8
  51. data/config/routes.rb +3 -3
  52. data/lib/spree/api/dependencies.rb +2 -2
  53. metadata +11 -11
  54. data/app/controllers/spree/api/v3/store/categories/products_controller.rb +0 -37
  55. data/app/serializers/spree/api/v3/admin/metafield_serializer.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 314545a7037d2f446a6d764b8d130a6ba14c89cfdb82c380132595f74aa62435
4
- data.tar.gz: 5976dd28153ffc543f658ca56fab3a2d97cefb162ec25481e192deb599743939
3
+ metadata.gz: 4646295f332844bfdbf16d865c67f5441d3418eb8136a18899f14e4651955ea5
4
+ data.tar.gz: 49693605ce4d13a68b9008d6677984812f9839e764758460597d08de211069f0
5
5
  SHA512:
6
- metadata.gz: 6856dc5fbe6411874444f3bc9ea16b4047ab11470d85b625daca9e054bc6289a37ac023a1fa899a6e9199aebfe1e6a2b1b4f3716611d34bc80ad829e0c57cf04
7
- data.tar.gz: ab453e9b05648c4ce61feca5febcaf815d898b17f098696b27dff28d006d015130987d9d8233532b5b74d4fb5c31f6e3f7d12ca4fac10d6ac070bfbb5a4f384b
6
+ metadata.gz: 74009ca81a6f9b0feef636f9868a8b415d098a729d68e5fbed018e3b8de8a89fc69db79265b6ba889b09895546e2d3c218327d89c349e8b22265d1e1c2ffb3db
7
+ data.tar.gz: 2ba9f53edc7610636143d874b5d2511a705098d54294ee5cdac318f0c6d45297c96d7fff8752747c7d34933449e616c0e9de044e260022ac6116cfca08d7229e
@@ -0,0 +1,20 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Store
5
+ class DataFeedsController < Store::BaseController
6
+ skip_before_action :authenticate_api_key!
7
+ skip_before_action :authenticate_user
8
+
9
+ # GET /api/v3/store/feeds/:slug.xml
10
+ def show
11
+ data_feed = current_store.data_feeds.active.find_by!(slug: params[:slug])
12
+ presenter = data_feed.class.presenter_class.new(data_feed)
13
+
14
+ render xml: presenter.call
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,13 +7,11 @@ module Spree
7
7
  class AllowedOriginSerializer < V3::BaseSerializer
8
8
  typelize store_id: :string
9
9
 
10
- attributes :id, :origin
10
+ attributes :id, :origin, created_at: :iso8601, updated_at: :iso8601
11
11
 
12
12
  attribute :store_id do |allowed_origin|
13
13
  allowed_origin.store&.prefixed_id
14
14
  end
15
-
16
- attributes created_at: :iso8601, updated_at: :iso8601
17
15
  end
18
16
  end
19
17
  end
@@ -8,7 +8,7 @@ module Spree
8
8
  typelize lft: :number, rgt: :number
9
9
 
10
10
  # Nested set columns for tree operations
11
- attributes :lft, :rgt
11
+ attributes :lft, :rgt, created_at: :iso8601, updated_at: :iso8601
12
12
 
13
13
  # Override inherited associations to use admin serializers
14
14
  one :parent,
@@ -24,8 +24,9 @@ module Spree
24
24
  if: proc { expand?('ancestors') }
25
25
 
26
26
  many :metafields,
27
- resource: Spree.api.admin_metafield_serializer,
28
- if: proc { expand?('metafields') }
27
+ key: :custom_fields,
28
+ resource: Spree.api.admin_custom_field_serializer,
29
+ if: proc { expand?('custom_fields') }
29
30
  end
30
31
  end
31
32
  end
@@ -0,0 +1,19 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API Custom Field Serializer
6
+ # Full custom field data including admin-only fields
7
+ class CustomFieldSerializer < V3::CustomFieldSerializer
8
+ typelize storefront_visible: :boolean
9
+
10
+ attributes created_at: :iso8601, updated_at: :iso8601
11
+
12
+ attribute :storefront_visible do |metafield|
13
+ metafield.display_on.in?(%w[both front_end])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -12,7 +12,8 @@ module Spree
12
12
 
13
13
  # Admin-only attributes
14
14
  attributes :login,
15
- last_sign_in_at: :iso8601, current_sign_in_at: :iso8601
15
+ last_sign_in_at: :iso8601, current_sign_in_at: :iso8601,
16
+ created_at: :iso8601, updated_at: :iso8601
16
17
 
17
18
  attribute :sign_in_count do |user|
18
19
  user.sign_in_count
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class DeliveryRateSerializer < V3::DeliveryRateSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  one :shipping_method, key: :delivery_method, resource: Spree.api.admin_delivery_method_serializer, if: proc { expand?('delivery_method') }
7
9
  end
8
10
  end
@@ -3,6 +3,7 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class DigitalLinkSerializer < V3::DigitalLinkSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
6
7
  end
7
8
  end
8
9
  end
@@ -3,6 +3,7 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class DiscountSerializer < V3::DiscountSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
6
7
  end
7
8
  end
8
9
  end
@@ -11,7 +11,8 @@ module Spree
11
11
  pre_tax_amount: :string
12
12
 
13
13
  attributes :adjustment_total, :additional_tax_total,
14
- :included_tax_total, :promo_total, :pre_tax_amount
14
+ :included_tax_total, :promo_total, :pre_tax_amount,
15
+ created_at: :iso8601, updated_at: :iso8601
15
16
 
16
17
  attribute :metadata do |shipment|
17
18
  shipment.metadata.presence
@@ -10,6 +10,8 @@ module Spree
10
10
  tax_category_id: [:string, nullable: true],
11
11
  order_id: [:string, nullable: true]
12
12
 
13
+ attributes created_at: :iso8601, updated_at: :iso8601
14
+
13
15
  attribute :metadata do |line_item|
14
16
  line_item.metadata.presence
15
17
  end
@@ -5,6 +5,8 @@ module Spree
5
5
  class MediaSerializer < V3::MediaSerializer
6
6
  typelize viewable_type: :string, viewable_id: :string
7
7
 
8
+ attributes created_at: :iso8601, updated_at: :iso8601
9
+
8
10
  attribute :viewable_id do |asset|
9
11
  asset.viewable&.prefixed_id
10
12
  end
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class OptionTypeSerializer < V3::OptionTypeSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  many :option_values,
7
9
  resource: Spree.api.admin_option_value_serializer,
8
10
  if: proc { expand?('option_values') }
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class OptionValueSerializer < V3::OptionValueSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  one :option_type,
7
9
  resource: Spree.api.admin_option_type_serializer,
8
10
  if: proc { expand?('option_type') }
@@ -6,7 +6,7 @@ module Spree
6
6
  # Full order data including admin-only fields
7
7
  class OrderSerializer < V3::OrderSerializer
8
8
 
9
- typelize channel: [:string, nullable: true], last_ip_address: [:string, nullable: true],
9
+ typelize last_ip_address: [:string, nullable: true],
10
10
  considered_risky: :boolean, confirmation_delivered: :boolean,
11
11
  store_owner_notification_delivered: :boolean,
12
12
  internal_note: [:string, nullable: true], approver_id: [:string, nullable: true],
@@ -17,10 +17,11 @@ module Spree
17
17
  metadata: 'Record<string, unknown> | null'
18
18
 
19
19
  # Admin-only attributes
20
- attributes :channel, :last_ip_address, :considered_risky,
20
+ attributes :last_ip_address, :considered_risky,
21
21
  :confirmation_delivered, :store_owner_notification_delivered,
22
22
  :internal_note, :payment_total, :display_payment_total,
23
- canceled_at: :iso8601, approved_at: :iso8601
23
+ canceled_at: :iso8601, approved_at: :iso8601,
24
+ created_at: :iso8601, updated_at: :iso8601
24
25
 
25
26
  attribute :metadata do |order|
26
27
  order.metadata.presence
@@ -10,7 +10,8 @@ module Spree
10
10
  cvv_response_code: [:string, nullable: true],
11
11
  cvv_response_message: [:string, nullable: true]
12
12
 
13
- attributes :avs_response, :cvv_response_code, :cvv_response_message
13
+ attributes :avs_response, :cvv_response_code, :cvv_response_message,
14
+ created_at: :iso8601, updated_at: :iso8601
14
15
 
15
16
  attribute :metadata do |payment|
16
17
  payment.metadata.presence
@@ -3,6 +3,7 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class PaymentSourceSerializer < V3::PaymentSourceSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
6
7
  end
7
8
  end
8
9
  end
@@ -13,7 +13,8 @@ module Spree
13
13
  deleted_at: [:string, nullable: true]
14
14
 
15
15
  # Admin-only attributes
16
- attributes :status, :make_active_at, :discontinue_on, deleted_at: :iso8601
16
+ attributes :status, :make_active_at, :discontinue_on, deleted_at: :iso8601,
17
+ created_at: :iso8601, updated_at: :iso8601
17
18
 
18
19
  attribute :cost_price do |product|
19
20
  product.master&.cost_price
@@ -54,8 +55,9 @@ module Spree
54
55
  if: proc { expand?('categories') }
55
56
 
56
57
  many :metafields,
57
- resource: Spree.api.admin_metafield_serializer,
58
- if: proc { expand?('metafields') }
58
+ key: :custom_fields,
59
+ resource: Spree.api.admin_custom_field_serializer,
60
+ if: proc { expand?('custom_fields') }
59
61
  end
60
62
  end
61
63
  end
@@ -7,6 +7,8 @@ module Spree
7
7
  refund_reason_id: [:string, nullable: true],
8
8
  reimbursement_id: [:string, nullable: true]
9
9
 
10
+ attributes created_at: :iso8601, updated_at: :iso8601
11
+
10
12
  one :payment,
11
13
  resource: Spree.api.admin_payment_serializer,
12
14
  if: proc { expand?('payment') }
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class ReimbursementSerializer < V3::ReimbursementSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  one :order,
7
9
  resource: Spree.api.admin_order_serializer,
8
10
  if: proc { expand?('order') }
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class ReturnAuthorizationSerializer < V3::ReturnAuthorizationSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  one :order,
7
9
  resource: Spree.api.admin_order_serializer,
8
10
  if: proc { expand?('order') }
@@ -3,6 +3,8 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class StockItemSerializer < V3::StockItemSerializer
6
+ attributes created_at: :iso8601, updated_at: :iso8601
7
+
6
8
  one :stock_location,
7
9
  resource: Spree.api.admin_stock_location_serializer,
8
10
  if: proc { expand?('stock_location') }
@@ -7,6 +7,8 @@ module Spree
7
7
  created_by_id: [:string, nullable: true],
8
8
  metadata: 'Record<string, unknown> | null'
9
9
 
10
+ attributes created_at: :iso8601, updated_at: :iso8601
11
+
10
12
  attribute :customer_id do |store_credit|
11
13
  store_credit.user&.prefixed_id
12
14
  end
@@ -13,11 +13,8 @@ module Spree
13
13
  deleted_at: [:string, nullable: true]
14
14
 
15
15
  # Admin-only attributes
16
- attributes :position, :tax_category_id, :cost_price, :cost_currency, deleted_at: :iso8601
17
-
18
- attribute :total_on_hand do |variant|
19
- variant.total_on_hand
20
- end
16
+ attributes :position, :total_on_hand, :tax_category_id, :cost_price, :cost_currency, deleted_at: :iso8601,
17
+ created_at: :iso8601, updated_at: :iso8601
21
18
 
22
19
  # Override inherited associations to use admin serializers
23
20
  one :primary_media,
@@ -37,8 +34,9 @@ module Spree
37
34
  if: proc { expand?('prices') }
38
35
 
39
36
  many :metafields,
40
- resource: Spree.api.admin_metafield_serializer,
41
- if: proc { expand?('metafields') }
37
+ key: :custom_fields,
38
+ resource: Spree.api.admin_custom_field_serializer,
39
+ if: proc { expand?('custom_fields') }
42
40
  end
43
41
  end
44
42
  end
@@ -38,8 +38,7 @@ module Spree
38
38
  :additional_tax_total, :display_additional_tax_total, :total, :display_total,
39
39
  :gift_card_total, :display_gift_card_total,
40
40
  :amount_due, :display_amount_due,
41
- :delivery_total, :display_delivery_total, :warnings,
42
- created_at: :iso8601, updated_at: :iso8601
41
+ :delivery_total, :display_delivery_total, :warnings
43
42
 
44
43
  attribute :store_credit_total do |order|
45
44
  order.total_applied_store_credit.to_s
@@ -11,8 +11,7 @@ module Spree
11
11
 
12
12
  attributes :name, :permalink, :position, :depth,
13
13
  :meta_title, :meta_description, :meta_keywords,
14
- :children_count,
15
- created_at: :iso8601, updated_at: :iso8601
14
+ :children_count
16
15
 
17
16
  attribute :parent_id do |category|
18
17
  category.parent&.prefixed_id
@@ -62,9 +61,9 @@ module Spree
62
61
  if: proc { expand?('ancestors') }
63
62
 
64
63
  many :public_metafields,
65
- key: :metafields,
66
- resource: Spree.api.metafield_serializer,
67
- if: proc { expand?('metafields') }
64
+ key: :custom_fields,
65
+ resource: Spree.api.custom_field_serializer,
66
+ if: proc { expand?('custom_fields') }
68
67
  end
69
68
  end
70
69
  end
@@ -1,9 +1,9 @@
1
1
  module Spree
2
2
  module Api
3
3
  module V3
4
- # Store API Metafield Serializer
5
- # Customer-facing metafield data (public metafields only)
6
- class MetafieldSerializer < BaseSerializer
4
+ # Store API Custom Field Serializer
5
+ # Customer-facing custom field data (public metafields only)
6
+ class CustomFieldSerializer < BaseSerializer
7
7
  typelize key: :string, name: :string, type: :string, value: :any
8
8
 
9
9
  attributes :name, :type
@@ -9,8 +9,7 @@ module Spree
9
9
  available_store_credit_total: :string, display_available_store_credit_total: :string,
10
10
  default_billing_address: { nullable: true }, default_shipping_address: { nullable: true }
11
11
 
12
- attributes :email, :first_name, :last_name, :phone, :accepts_email_marketing,
13
- created_at: :iso8601, updated_at: :iso8601
12
+ attributes :email, :first_name, :last_name, :phone, :accepts_email_marketing
14
13
 
15
14
  attribute :available_store_credit_total do |user, params|
16
15
  store = params&.dig(:store) || Spree::Current.store
@@ -6,8 +6,7 @@ module Spree
6
6
  download_url: :string,
7
7
  authorizable: :boolean, expired: :boolean, access_limit_exceeded: :boolean
8
8
 
9
- attributes :access_counter, :filename, :content_type,
10
- created_at: :iso8601, updated_at: :iso8601
9
+ attributes :access_counter, :filename, :content_type
11
10
 
12
11
  attribute :download_url do |digital_link|
13
12
  Spree::Core::Engine.routes.url_helpers.api_v3_store_digital_download_path(token: digital_link.token)
@@ -9,8 +9,7 @@ module Spree
9
9
  items: 'Array<{ item_id: string; variant_id: string; quantity: number }>'
10
10
 
11
11
  attributes :number, :tracking, :tracking_url,
12
- :cost, :display_cost,
13
- created_at: :iso8601, updated_at: :iso8601
12
+ :cost, :display_cost
14
13
 
15
14
  attribute :status do |shipment|
16
15
  shipment.state
@@ -55,7 +55,6 @@ module Spree
55
55
  gift_card.active?
56
56
  end
57
57
 
58
- attributes created_at: :iso8601, updated_at: :iso8601
59
58
  end
60
59
  end
61
60
  end
@@ -26,8 +26,7 @@ module Spree
26
26
  :discount_total, :display_discount_total,
27
27
  :pre_tax_amount, :display_pre_tax_amount,
28
28
  :discounted_amount, :display_discounted_amount,
29
- :display_compare_at_amount,
30
- created_at: :iso8601, updated_at: :iso8601
29
+ :display_compare_at_amount
31
30
 
32
31
  # Return compare_at_amount as string, nil if zero
33
32
  attribute :compare_at_amount do |line_item|
@@ -29,8 +29,7 @@ module Spree
29
29
  end
30
30
 
31
31
  attributes :position, :alt, :media_type,
32
- :focal_point_x, :focal_point_y, :external_video_url,
33
- created_at: :iso8601, updated_at: :iso8601
32
+ :focal_point_x, :focal_point_y, :external_video_url
34
33
 
35
34
  attribute :original_url do |asset|
36
35
  image_url_for(asset)
@@ -33,7 +33,7 @@ module Spree
33
33
  :gift_card_total, :display_gift_card_total,
34
34
  :amount_due, :display_amount_due,
35
35
  :delivery_total, :display_delivery_total, :fulfillment_status, :payment_status,
36
- completed_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
36
+ completed_at: :iso8601
37
37
 
38
38
  attribute :store_credit_total do |order|
39
39
  order.total_applied_store_credit.to_s
@@ -12,8 +12,7 @@ module Spree
12
12
  payment.payment_method&.prefixed_id
13
13
  end
14
14
 
15
- attributes :response_code, :number, :amount, :display_amount,
16
- created_at: :iso8601, updated_at: :iso8601
15
+ attributes :response_code, :number, :amount, :display_amount
17
16
 
18
17
  attribute :status do |payment|
19
18
  payment.state
@@ -9,7 +9,7 @@ module Spree
9
9
 
10
10
  attributes :status, :currency, :external_id, :external_data,
11
11
  :customer_external_id,
12
- expires_at: :iso8601, created_at: :iso8601, updated_at: :iso8601
12
+ expires_at: :iso8601
13
13
 
14
14
  attribute :amount do |session|
15
15
  session.amount&.to_s
@@ -7,8 +7,7 @@ module Spree
7
7
  payment_method_id: [:string, nullable: true], payment_source_id: [:string, nullable: true],
8
8
  payment_source_type: [:string, nullable: true], customer_id: [:string, nullable: true]
9
9
 
10
- attributes :status, :external_id, :external_client_secret, :external_data,
11
- created_at: :iso8601, updated_at: :iso8601
10
+ attributes :status, :external_id, :external_client_secret, :external_data
12
11
 
13
12
  attribute :payment_method_id do |session|
14
13
  session.payment_method&.prefixed_id
@@ -15,7 +15,6 @@ module Spree
15
15
  policy.body&.body&.to_s.to_s
16
16
  end
17
17
 
18
- attributes created_at: :iso8601, updated_at: :iso8601
19
18
  end
20
19
  end
21
20
  end
@@ -18,7 +18,7 @@ module Spree
18
18
  attributes :name, :slug,
19
19
  :meta_description, :meta_keywords,
20
20
  :variant_count,
21
- available_on: :iso8601, created_at: :iso8601, updated_at: :iso8601
21
+ available_on: :iso8601
22
22
 
23
23
  attribute :purchasable do |product|
24
24
  product.purchasable?
@@ -108,9 +108,9 @@ module Spree
108
108
  if: proc { expand?('categories') }
109
109
 
110
110
  many :public_metafields,
111
- key: :metafields,
112
- resource: Spree.api.metafield_serializer,
113
- if: proc { expand?('metafields') }
111
+ key: :custom_fields,
112
+ resource: Spree.api.custom_field_serializer,
113
+ if: proc { expand?('custom_fields') }
114
114
 
115
115
  typelize prior_price: ['PriceHistory', nullable: true]
116
116
 
@@ -8,7 +8,7 @@ module Spree
8
8
  payment_id: [:string, nullable: true], refund_reason_id: [:string, nullable: true],
9
9
  reimbursement_id: [:string, nullable: true]
10
10
 
11
- attributes :transaction_id, created_at: :iso8601, updated_at: :iso8601
11
+ attributes :transaction_id
12
12
 
13
13
  attribute :amount do |refund|
14
14
  refund.amount&.to_s
@@ -8,8 +8,7 @@ module Spree
8
8
  total: [:string, nullable: true],
9
9
  order_id: [:string, nullable: true], customer_return_id: [:string, nullable: true]
10
10
 
11
- attributes :number, :reimbursement_status,
12
- created_at: :iso8601, updated_at: :iso8601
11
+ attributes :number, :reimbursement_status
13
12
 
14
13
  attribute :total do |reimbursement|
15
14
  reimbursement.total&.to_s
@@ -9,8 +9,7 @@ module Spree
9
9
  currency: [:string, nullable: true],
10
10
  date_from: [:string, nullable: true], date_to: [:string, nullable: true]
11
11
 
12
- attributes :type, :currency,
13
- created_at: :iso8601, updated_at: :iso8601
12
+ attributes :type, :currency
14
13
 
15
14
  attribute :user_id do |report|
16
15
  report.user&.prefixed_id
@@ -8,7 +8,7 @@ module Spree
8
8
  order_id: [:string, nullable: true], stock_location_id: [:string, nullable: true],
9
9
  return_authorization_reason_id: [:string, nullable: true]
10
10
 
11
- attributes :number, created_at: :iso8601, updated_at: :iso8601
11
+ attributes :number
12
12
 
13
13
  attribute :status do |return_authorization|
14
14
  return_authorization.state.to_s
@@ -7,8 +7,7 @@ module Spree
7
7
  typelize count_on_hand: :number, backorderable: :boolean,
8
8
  stock_location_id: [:string, nullable: true], variant_id: [:string, nullable: true]
9
9
 
10
- attributes :count_on_hand, :backorderable,
11
- created_at: :iso8601, updated_at: :iso8601
10
+ attributes :count_on_hand, :backorderable
12
11
 
13
12
  attribute :stock_location_id do |stock_item|
14
13
  stock_item.stock_location&.prefixed_id
@@ -16,8 +16,7 @@ module Spree
16
16
  variant.product&.prefixed_id
17
17
  end
18
18
 
19
- attributes :sku, :options_text, :track_inventory, :media_count,
20
- created_at: :iso8601, updated_at: :iso8601
19
+ attributes :sku, :options_text, :track_inventory, :media_count
21
20
 
22
21
  # Main variant image URL for listings (cached primary_media)
23
22
  attribute :thumbnail_url do |variant|
@@ -82,9 +81,9 @@ module Spree
82
81
  many :option_values, resource: Spree.api.option_value_serializer
83
82
 
84
83
  many :public_metafields,
85
- key: :metafields,
86
- resource: Spree.api.metafield_serializer,
87
- if: proc { expand?('metafields') }
84
+ key: :custom_fields,
85
+ resource: Spree.api.custom_field_serializer,
86
+ if: proc { expand?('custom_fields') }
88
87
 
89
88
  typelize prior_price: ['PriceHistory', nullable: true]
90
89
 
@@ -12,8 +12,7 @@ module Spree
12
12
  wished_item.wishlist&.prefixed_id
13
13
  end
14
14
 
15
- attributes :quantity,
16
- created_at: :iso8601, updated_at: :iso8601
15
+ attributes :quantity
17
16
 
18
17
  one :variant, resource: Spree.api.variant_serializer
19
18
  end
@@ -4,8 +4,7 @@ module Spree
4
4
  class WishlistSerializer < BaseSerializer
5
5
  typelize name: :string, token: :string, is_default: :boolean, is_private: :boolean
6
6
 
7
- attributes :name, :token,
8
- created_at: :iso8601, updated_at: :iso8601
7
+ attributes :name, :token
9
8
 
10
9
  attribute :is_default do |wishlist|
11
10
  wishlist.is_default?
@@ -2,13 +2,17 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  class FiltersAggregator
5
- # @param scope [ActiveRecord::Relation] Base product scope (already filtered by store, availability, category, etc.)
5
+ # @param scope [ActiveRecord::Relation] Base product scope (fully filtered, including option values)
6
6
  # @param currency [String] Currency for price range
7
7
  # @param category [Spree::Category, nil] Optional category for default_sort and category filtering context
8
- def initialize(scope:, currency:, category: nil)
8
+ # @param option_value_ids [Array<String>] Currently selected option value prefixed IDs (for disjunctive facet counts)
9
+ # @param scope_before_options [ActiveRecord::Relation] Scope before option value filters (for disjunctive counts)
10
+ def initialize(scope:, currency:, category: nil, option_value_ids: [], scope_before_options: nil)
9
11
  @scope = scope
10
12
  @currency = currency
11
13
  @category = category
14
+ @option_value_ids = option_value_ids
15
+ @scope_before_options = scope_before_options || scope
12
16
  end
13
17
 
14
18
  def call
@@ -78,25 +82,26 @@ module Spree
78
82
 
79
83
  def option_type_filters
80
84
  Spree::OptionType.filterable.includes(:option_values).order(:position).filter_map do |option_type|
81
- values = option_type.option_values.for_products(@scope).distinct.order(:position)
85
+ # Disjunctive: count against scope WITHOUT this option type's filter
86
+ count_scope = disjunctive_scope_for(option_type)
87
+ values = option_type.option_values.for_products(count_scope).distinct.order(:position)
82
88
  next if values.empty?
83
89
 
90
+ count_ids = count_scope.reorder('').distinct.pluck(:id)
91
+
84
92
  {
85
93
  id: option_type.prefixed_id,
86
94
  type: 'option',
87
95
  name: option_type.name,
88
96
  label: option_type.label,
89
- options: values.map { |ov| option_value_data(option_type, ov) }
97
+ options: values.map { |ov| option_value_data(count_ids, ov) }
90
98
  }
91
99
  end
92
100
  end
93
101
 
94
- def option_value_data(option_type, option_value)
95
- # Count products in scope that have this option value
96
- # We use a subquery approach to avoid GROUP BY conflicts when scope includes joins (like in_category)
97
- # Join directly to option_value_variants for efficiency (skips joining through option_values table)
102
+ def option_value_data(product_ids, option_value)
98
103
  count = Spree::Product
99
- .where(id: base_scope_product_ids)
104
+ .where(id: product_ids)
100
105
  .joins(:option_value_variants)
101
106
  .where(Spree::OptionValueVariant.table_name => { option_value_id: option_value.id })
102
107
  .distinct
@@ -111,8 +116,38 @@ module Spree
111
116
  }
112
117
  end
113
118
 
114
- def base_scope_product_ids
115
- @base_scope_product_ids ||= @scope.reorder('').distinct.pluck(:id)
119
+ # Returns the scope with all option type filters EXCEPT the given one applied.
120
+ # This gives disjunctive counts: selecting Blue still shows Red's true count.
121
+ def disjunctive_scope_for(option_type)
122
+ return @scope_before_options if grouped_selected_options.empty?
123
+
124
+ other_groups = grouped_selected_options.except(option_type.id)
125
+
126
+ # If this type has selections but no other types do, use scope before any option filters
127
+ return @scope_before_options if other_groups.empty?
128
+
129
+ # Rebuild: start from scope before options, apply only other option types
130
+ scope = @scope_before_options
131
+ other_groups.each_value do |ov_ids|
132
+ matching = Spree::Variant.where(deleted_at: nil)
133
+ .joins(:option_value_variants)
134
+ .where(Spree::OptionValueVariant.table_name => { option_value_id: ov_ids })
135
+ .select(:product_id)
136
+ scope = scope.where(id: matching)
137
+ end
138
+ scope
139
+ end
140
+
141
+ # Group selected option value IDs by option type (cached, single query)
142
+ def grouped_selected_options
143
+ @grouped_selected_options ||= begin
144
+ return {} if @option_value_ids.blank?
145
+
146
+ decoded = @option_value_ids.filter_map { |id| Spree::OptionValue.decode_prefixed_id(id) }
147
+ return {} if decoded.empty?
148
+
149
+ Spree::OptionValue.where(id: decoded).group_by(&:option_type_id).transform_values { |ovs| ovs.map(&:id) }
150
+ end
116
151
  end
117
152
 
118
153
  def category_filter
@@ -49,22 +49,31 @@ module Spree
49
49
  private
50
50
 
51
51
  def make_request
52
- SsrfFilter.post(
53
- @delivery.url,
54
- headers: {
55
- 'Content-Type' => 'application/json',
56
- 'User-Agent' => 'Spree-Webhooks/1.0',
57
- 'X-Spree-Webhook-Signature' => generate_signature,
58
- 'X-Spree-Webhook-Timestamp' => webhook_timestamp.to_s,
59
- 'X-Spree-Webhook-Event' => @delivery.event_name
60
- },
61
- body: @delivery.payload.to_json,
62
- http_options: {
63
- open_timeout: TIMEOUT,
64
- read_timeout: TIMEOUT,
65
- verify_mode: ssl_verify_mode
66
- }
67
- )
52
+ headers = {
53
+ 'Content-Type' => 'application/json',
54
+ 'User-Agent' => 'Spree-Webhooks/1.0',
55
+ 'X-Spree-Webhook-Signature' => generate_signature,
56
+ 'X-Spree-Webhook-Timestamp' => webhook_timestamp.to_s,
57
+ 'X-Spree-Webhook-Event' => @delivery.event_name
58
+ }
59
+ body = @delivery.payload.to_json
60
+ http_options = { open_timeout: TIMEOUT, read_timeout: TIMEOUT, verify_mode: ssl_verify_mode }
61
+
62
+ # SSRF protection is disabled in development so webhooks can reach
63
+ # localhost / host.docker.internal (the storefront running on the host).
64
+ if Rails.env.development?
65
+ uri = URI.parse(@delivery.url)
66
+ http = Net::HTTP.new(uri.host, uri.port)
67
+ http.use_ssl = uri.scheme == 'https'
68
+ http_options.each { |k, v| http.send(:"#{k}=", v) }
69
+
70
+ request = Net::HTTP::Post.new(uri.request_uri)
71
+ headers.each { |k, v| request[k] = v }
72
+ request.body = body
73
+ http.request(request)
74
+ else
75
+ SsrfFilter.post(@delivery.url, headers: headers, body: body, http_options: http_options)
76
+ end
68
77
  end
69
78
 
70
79
  def generate_signature
@@ -21,13 +21,15 @@ module Spree
21
21
  return unless Spree::Api::Config.webhooks_enabled
22
22
  return if event.store_id.blank?
23
23
 
24
- # Find all active endpoints for this store subscribed to this event
25
- endpoints = Spree::WebhookEndpoint.active.where(store_id: event.store_id).select { |endpoint| endpoint.subscribed_to?(event.name) }
24
+ # Only load the columns we need for matching and delivery
25
+ endpoints = Spree::WebhookEndpoint
26
+ .enabled
27
+ .where(store_id: event.store_id)
28
+ .select(:id, :subscriptions)
26
29
 
27
- return if endpoints.empty?
28
-
29
- # Queue delivery for each endpoint
30
30
  endpoints.each do |endpoint|
31
+ next unless endpoint.subscribed_to?(event.name)
32
+
31
33
  queue_delivery(endpoint, event)
32
34
  end
33
35
  rescue StandardError => e
@@ -38,17 +40,25 @@ module Spree
38
40
  private
39
41
 
40
42
  def queue_delivery(endpoint, event)
41
- # Build base payload (without delivery ID)
42
43
  payload = build_payload(event)
43
44
 
44
- # Create delivery record
45
+ # Deduplicate: skip if we already have a delivery for this event + endpoint
46
+ if event.id.present?
47
+ return if Spree::WebhookDelivery.exists?(
48
+ webhook_endpoint_id: endpoint.id,
49
+ event_id: event.id
50
+ )
51
+ end
52
+
45
53
  delivery = endpoint.webhook_deliveries.create!(
46
54
  event_name: event.name,
55
+ event_id: event.id,
47
56
  payload: payload
48
57
  )
49
58
 
50
- # Queue the delivery job
51
59
  Spree::WebhookDeliveryJob.perform_later(delivery.id)
60
+ rescue ActiveRecord::RecordNotUnique
61
+ # Race condition: another thread already created this delivery — safe to ignore
52
62
  rescue StandardError => e
53
63
  Rails.logger.error "[Spree Webhooks] Error queuing delivery for endpoint #{endpoint.id}: #{e.message}"
54
64
  Rails.error.report(e)
data/config/routes.rb CHANGED
@@ -27,9 +27,7 @@ Spree::Core::Engine.add_routes do
27
27
  get :filters, to: 'products/filters#index'
28
28
  end
29
29
  end
30
- resources :categories, only: [:index, :show], id: /.+/ do
31
- resources :products, only: [:index], controller: 'categories/products'
32
- end
30
+ resources :categories, only: [:index, :show], id: /.+/
33
31
 
34
32
  # Carts
35
33
  resources :carts, only: [:index, :show, :create, :update, :destroy] do
@@ -88,6 +86,8 @@ Spree::Core::Engine.add_routes do
88
86
  # Access via token in URL
89
87
  get 'digitals/:token', to: 'digitals#show', as: :digital_download
90
88
 
89
+ # Data Feeds (public, no auth required)
90
+ resources :feeds, only: [:show], controller: 'data_feeds', param: :slug
91
91
  end
92
92
 
93
93
  # Webhooks (outside of store namespace — no API key authentication)
@@ -131,7 +131,7 @@ module Spree
131
131
  currency_serializer: 'Spree::Api::V3::CurrencySerializer',
132
132
  locale_serializer: 'Spree::Api::V3::LocaleSerializer',
133
133
  policy_serializer: 'Spree::Api::V3::PolicySerializer',
134
- metafield_serializer: 'Spree::Api::V3::MetafieldSerializer',
134
+ custom_field_serializer: 'Spree::Api::V3::CustomFieldSerializer',
135
135
  shipping_category_serializer: 'Spree::Api::V3::ShippingCategorySerializer',
136
136
  tax_category_serializer: 'Spree::Api::V3::TaxCategorySerializer',
137
137
 
@@ -163,7 +163,7 @@ module Spree
163
163
  admin_variant_serializer: 'Spree::Api::V3::Admin::VariantSerializer',
164
164
  admin_price_serializer: 'Spree::Api::V3::Admin::PriceSerializer',
165
165
  admin_price_history_serializer: 'Spree::Api::V3::Admin::PriceHistorySerializer',
166
- admin_metafield_serializer: 'Spree::Api::V3::Admin::MetafieldSerializer',
166
+ admin_custom_field_serializer: 'Spree::Api::V3::Admin::CustomFieldSerializer',
167
167
  admin_category_serializer: 'Spree::Api::V3::Admin::CategorySerializer',
168
168
  admin_line_item_serializer: 'Spree::Api::V3::Admin::LineItemSerializer',
169
169
  admin_option_type_serializer: 'Spree::Api::V3::Admin::OptionTypeSerializer',
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.4.0.rc3
4
+ version: 5.4.0.rc4.1
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-03-25 00:00:00.000000000 Z
11
+ date: 2026-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rswag-specs
@@ -72,28 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.10.0
75
+ version: 0.11.0
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: 0.10.0
82
+ version: 0.11.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: spree_core
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: 5.4.0.rc3
89
+ version: 5.4.0.rc4.1
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - '='
95
95
  - !ruby/object:Gem::Version
96
- version: 5.4.0.rc3
96
+ version: 5.4.0.rc4.1
97
97
  description: Spree's API
98
98
  email:
99
99
  - hello@spreecommerce.org
@@ -129,7 +129,6 @@ files:
129
129
  - app/controllers/spree/api/v3/store/carts/payments_controller.rb
130
130
  - app/controllers/spree/api/v3/store/carts/store_credits_controller.rb
131
131
  - app/controllers/spree/api/v3/store/carts_controller.rb
132
- - app/controllers/spree/api/v3/store/categories/products_controller.rb
133
132
  - app/controllers/spree/api/v3/store/categories_controller.rb
134
133
  - app/controllers/spree/api/v3/store/countries_controller.rb
135
134
  - app/controllers/spree/api/v3/store/currencies_controller.rb
@@ -141,6 +140,7 @@ files:
141
140
  - app/controllers/spree/api/v3/store/customer/payment_setup_sessions_controller.rb
142
141
  - app/controllers/spree/api/v3/store/customer/store_credits_controller.rb
143
142
  - app/controllers/spree/api/v3/store/customers_controller.rb
143
+ - app/controllers/spree/api/v3/store/data_feeds_controller.rb
144
144
  - app/controllers/spree/api/v3/store/digitals_controller.rb
145
145
  - app/controllers/spree/api/v3/store/locales_controller.rb
146
146
  - app/controllers/spree/api/v3/store/markets/countries_controller.rb
@@ -163,6 +163,7 @@ files:
163
163
  - app/serializers/spree/api/v3/admin/asset_serializer.rb
164
164
  - app/serializers/spree/api/v3/admin/category_serializer.rb
165
165
  - app/serializers/spree/api/v3/admin/credit_card_serializer.rb
166
+ - app/serializers/spree/api/v3/admin/custom_field_serializer.rb
166
167
  - app/serializers/spree/api/v3/admin/customer_serializer.rb
167
168
  - app/serializers/spree/api/v3/admin/delivery_method_serializer.rb
168
169
  - app/serializers/spree/api/v3/admin/delivery_rate_serializer.rb
@@ -172,7 +173,6 @@ files:
172
173
  - app/serializers/spree/api/v3/admin/gift_card_serializer.rb
173
174
  - app/serializers/spree/api/v3/admin/line_item_serializer.rb
174
175
  - app/serializers/spree/api/v3/admin/media_serializer.rb
175
- - app/serializers/spree/api/v3/admin/metafield_serializer.rb
176
176
  - app/serializers/spree/api/v3/admin/option_type_serializer.rb
177
177
  - app/serializers/spree/api/v3/admin/option_value_serializer.rb
178
178
  - app/serializers/spree/api/v3/admin/order_serializer.rb
@@ -198,6 +198,7 @@ files:
198
198
  - app/serializers/spree/api/v3/country_serializer.rb
199
199
  - app/serializers/spree/api/v3/credit_card_serializer.rb
200
200
  - app/serializers/spree/api/v3/currency_serializer.rb
201
+ - app/serializers/spree/api/v3/custom_field_serializer.rb
201
202
  - app/serializers/spree/api/v3/customer_return_serializer.rb
202
203
  - app/serializers/spree/api/v3/customer_serializer.rb
203
204
  - app/serializers/spree/api/v3/delivery_method_serializer.rb
@@ -216,7 +217,6 @@ files:
216
217
  - app/serializers/spree/api/v3/locale_serializer.rb
217
218
  - app/serializers/spree/api/v3/market_serializer.rb
218
219
  - app/serializers/spree/api/v3/media_serializer.rb
219
- - app/serializers/spree/api/v3/metafield_serializer.rb
220
220
  - app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb
221
221
  - app/serializers/spree/api/v3/option_type_serializer.rb
222
222
  - app/serializers/spree/api/v3/option_value_serializer.rb
@@ -274,9 +274,9 @@ licenses:
274
274
  - BSD-3-Clause
275
275
  metadata:
276
276
  bug_tracker_uri: https://github.com/spree/spree/issues
277
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc3
277
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.4.0.rc4.1
278
278
  documentation_uri: https://docs.spreecommerce.org/
279
- source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc3
279
+ source_code_uri: https://github.com/spree/spree/tree/v5.4.0.rc4.1
280
280
  post_install_message:
281
281
  rdoc_options: []
282
282
  require_paths:
@@ -1,37 +0,0 @@
1
- module Spree
2
- module Api
3
- module V3
4
- module Store
5
- module Categories
6
- class ProductsController < Store::ProductsController
7
- before_action :set_category
8
-
9
- protected
10
-
11
- def set_category
12
- @category = find_category
13
- end
14
-
15
- def scope
16
- super.in_category(@category)
17
- end
18
-
19
- private
20
-
21
- def find_category
22
- id = params[:category_id]
23
- category_scope = Spree::Category.for_store(current_store).accessible_by(current_ability, :show)
24
- category_scope = category_scope.i18n if Spree::Category.include?(Spree::TranslatableResource)
25
-
26
- if id.to_s.start_with?('ctg_')
27
- category_scope.find_by_prefix_id!(id)
28
- else
29
- find_with_fallback_default_locale { category_scope.i18n.find_by!(permalink: id) }
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,15 +0,0 @@
1
- module Spree
2
- module Api
3
- module V3
4
- module Admin
5
- # Admin API Metafield Serializer
6
- # Full metafield data including admin-only fields
7
- class MetafieldSerializer < V3::MetafieldSerializer
8
- typelize display_on: :string
9
-
10
- attributes :display_on
11
- end
12
- end
13
- end
14
- end
15
- end