spree_api 6.0.0.rc5 → 6.0.0.rc6

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +23 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb +26 -0
  4. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -0
  5. data/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +14 -2
  6. data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +15 -2
  7. data/app/controllers/concerns/spree/api/v3/digital_assets_resource.rb +68 -0
  8. data/app/controllers/concerns/spree/api/v3/error_handler.rb +3 -0
  9. data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +27 -1
  10. data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +6 -1
  11. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +4 -1
  12. data/app/controllers/spree/api/v3/admin/auth_controller.rb +32 -5
  13. data/app/controllers/spree/api/v3/admin/commission_lines_controller.rb +2 -2
  14. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +4 -7
  15. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +4 -0
  16. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +1 -1
  17. data/app/controllers/spree/api/v3/admin/customers_controller.rb +13 -8
  18. data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +1 -1
  19. data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +1 -1
  20. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +2 -0
  21. data/app/controllers/spree/api/v3/admin/me_controller.rb +2 -0
  22. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +13 -0
  23. data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +2 -0
  24. data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +9 -0
  25. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
  26. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +7 -4
  27. data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +2 -0
  28. data/app/controllers/spree/api/v3/admin/orders_controller.rb +10 -2
  29. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +7 -1
  30. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +18 -0
  31. data/app/controllers/spree/api/v3/admin/product_types_controller.rb +12 -1
  32. data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +5 -109
  33. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +4 -1
  34. data/app/controllers/spree/api/v3/admin/products_controller.rb +28 -3
  35. data/app/controllers/spree/api/v3/admin/sellers/payouts_controller.rb +4 -3
  36. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +3 -2
  37. data/app/controllers/spree/api/v3/admin/store_controller.rb +7 -0
  38. data/app/controllers/spree/api/v3/admin/tags_controller.rb +11 -9
  39. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +5 -2
  40. data/app/controllers/spree/api/v3/resource_controller.rb +15 -2
  41. data/app/controllers/spree/api/v3/seller/auth_controller.rb +10 -0
  42. data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +2 -0
  43. data/app/controllers/spree/api/v3/seller/me_controller.rb +4 -0
  44. data/app/controllers/spree/api/v3/seller/orders_controller.rb +11 -3
  45. data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +5 -1
  46. data/app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb +67 -0
  47. data/app/controllers/spree/api/v3/seller/products_controller.rb +4 -2
  48. data/app/controllers/spree/api/v3/seller/team_controller.rb +1 -1
  49. data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +9 -1
  50. data/app/controllers/spree/api/v3/store/carts_controller.rb +14 -0
  51. data/app/controllers/spree/api/v3/store/customers_controller.rb +13 -1
  52. data/app/controllers/spree/api/v3/store/delivery_methods_controller.rb +6 -1
  53. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +19 -4
  54. data/app/request_schemas/spree/api/v3/digital_asset_request_schema.rb +25 -0
  55. data/app/serializers/concerns/spree/api/v3/digital_asset_details.rb +70 -0
  56. data/app/serializers/spree/api/v3/admin/delivery_origin_group_serializer.rb +2 -2
  57. data/app/serializers/spree/api/v3/admin/delivery_profile_serializer.rb +3 -5
  58. data/app/serializers/spree/api/v3/admin/digital_asset_serializer.rb +3 -47
  59. data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +1 -0
  60. data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +1 -1
  61. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +8 -3
  62. data/app/serializers/spree/api/v3/admin/price_serializer.rb +1 -1
  63. data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +2 -2
  64. data/app/serializers/spree/api/v3/admin/seller_serializer.rb +1 -3
  65. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +3 -5
  66. data/app/serializers/spree/api/v3/base_serializer.rb +6 -1
  67. data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +5 -1
  68. data/app/serializers/spree/api/v3/seller/digital_asset_serializer.rb +16 -0
  69. data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -2
  70. data/app/serializers/spree/api/v3/seller/store_serializer.rb +18 -0
  71. data/app/services/spree/api/v3/filters_aggregator.rb +63 -18
  72. data/config/locales/en.yml +1 -0
  73. data/config/routes.rb +3 -0
  74. data/lib/spree/api/dependencies.rb +2 -2
  75. data/lib/spree/api/engine.rb +6 -0
  76. data/lib/spree/api/middleware/no_index.rb +33 -0
  77. data/lib/spree/api/openapi/schema_helper.rb +2 -1
  78. data/lib/spree/api/testing_support/v3/base.rb +18 -0
  79. metadata +14 -6
@@ -251,30 +251,75 @@ module Spree
251
251
  def category_filter
252
252
  return nil if @category.nil?
253
253
 
254
- # Get child categories at the next depth level
255
- child_categories = @category.children.order(:lft).select do |child|
256
- # Only include categories that have products in the current scope
257
- @scope.in_category(child).exists?
254
+ children = @category.children.order(:lft).to_a
255
+ return nil if children.empty?
256
+
257
+ counts = product_counts_by_child_category(children)
258
+ # A child with nothing in the current scope is not offered as a
259
+ # filter, which is what the per-child `exists?` decided before.
260
+ options = children.filter_map do |child|
261
+ count = counts[child.id]
262
+ next if count.nil? || count.zero?
263
+
264
+ {
265
+ id: child.prefixed_id,
266
+ name: child.name,
267
+ permalink: child.permalink,
268
+ count: count
269
+ }
258
270
  end
259
271
 
260
- return nil if child_categories.empty?
272
+ return nil if options.empty?
261
273
 
262
- {
263
- id: 'categories',
264
- type: 'category',
265
- options: child_categories.map { |c| category_option_data(c) }
266
- }
274
+ { id: 'categories', type: 'category', options: options }
267
275
  end
268
276
 
269
- def category_option_data(category)
270
- count = @scope.in_category(category).distinct.count
277
+ # Distinct product counts for each child category, counting its whole
278
+ # subtree, in one query rather than two per child (an existence probe
279
+ # and a count). A category sidebar is drawn on every filtered
280
+ # storefront request, so the per-child pair is paid on each one.
281
+ #
282
+ # @param children [Array<Spree::Category>]
283
+ # @return [Hash{Integer => Integer}] child category id => product count
284
+ def product_counts_by_child_category(children)
285
+ descendant_ids_by_child = descendant_ids_for(children)
286
+ all_descendant_ids = descendant_ids_by_child.values.flatten.uniq
287
+ return {} if all_descendant_ids.empty?
288
+
289
+ product_categories = Spree::ProductCategory.table_name
290
+ # product id => the categories it sits in, narrowed to this subtree
291
+ pairs = @scope.reorder('').joins(:product_categories).
292
+ where("#{product_categories}.category_id" => all_descendant_ids).
293
+ distinct.
294
+ pluck("#{product_categories}.category_id", "#{product_categories}.product_id")
295
+
296
+ product_ids_by_category = Hash.new { |hash, key| hash[key] = [] }
297
+ pairs.each { |category_id, product_id| product_ids_by_category[category_id] << product_id }
298
+
299
+ descendant_ids_by_child.transform_values do |descendant_ids|
300
+ descendant_ids.flat_map { |id| product_ids_by_category[id] }.uniq.size
301
+ end
302
+ end
271
303
 
272
- {
273
- id: category.prefixed_id,
274
- name: category.name,
275
- permalink: category.permalink,
276
- count: count
277
- }
304
+ # Every child's subtree ids in one query. A nested set stores a
305
+ # subtree as an `lft`/`rgt` range, so one read of the categories
306
+ # spanned by the children answers for all of them — asking each child
307
+ # separately is a query per filter option.
308
+ #
309
+ # @param children [Array<Spree::Category>]
310
+ # @return [Hash{Integer => Array<Integer>}] child id => subtree ids
311
+ def descendant_ids_for(children)
312
+ bounds = children.to_h { |child| [child.id, (child.lft..child.rgt)] }
313
+
314
+ rows = Spree::Category.
315
+ where(store_id: children.first.store_id).
316
+ where(lft: children.map(&:lft).min..children.map(&:rgt).max).
317
+ pluck(:id, :lft)
318
+
319
+ children.to_h do |child|
320
+ range = bounds[child.id]
321
+ [child.id, rows.filter_map { |id, lft| id if range.cover?(lft) }]
322
+ end
278
323
  end
279
324
  end
280
325
  end
@@ -2,6 +2,7 @@
2
2
  en:
3
3
  spree:
4
4
  api:
5
+ account_deactivated: This account has been deactivated. Contact an administrator to restore access.
5
6
  cannot_delete: "%{model} cannot be deleted"
6
7
  cannot_modify_seller_import: This import belongs to a seller. It can be viewed here, but only the seller can retry or delete it.
7
8
  current_password_invalid: Current password is invalid or missing
data/config/routes.rb CHANGED
@@ -910,6 +910,9 @@ Spree::Core::Engine.add_routes do
910
910
  post :bulk_status_update
911
911
  delete :bulk_destroy
912
912
  end
913
+
914
+ resources :digital_assets, controller: 'products/digital_assets',
915
+ only: [:index, :create, :show, :update, :destroy]
913
916
  end
914
917
 
915
918
 
@@ -51,8 +51,8 @@ module Spree
51
51
  order_line_item fulfillment fulfillment_item delivery delivery_rate shipping_label return
52
52
  return_line_item exchange exchange_line_item claim claim_line_item reason package_type stock_location
53
53
  requirement_custom_field tax_identifier requirement_status import import_row import_mapping
54
- requirement_submission export transfer payout balance payment_split price price_history file_upload
55
- file_upload_target].freeze
54
+ requirement_submission export transfer payout balance payment_split price price_history file_upload digital_asset
55
+ file_upload_target store].freeze
56
56
 
57
57
  INJECTION_POINTS_WITH_DEFAULTS = {
58
58
  **STORE_SERIALIZERS.to_h { |name| [:"#{name}_serializer", "Spree::Api::V3::#{name.to_s.camelize}Serializer"] },
@@ -49,6 +49,12 @@ module Spree
49
49
  app.middleware.insert_before Rack::Runtime, Spree::Api::Middleware::RequestSizeLimit
50
50
  end
51
51
 
52
+ # Outside ShowExceptions, so an unknown API path's 404 carries it too.
53
+ initializer 'spree.api.no_index' do |app|
54
+ require_relative 'middleware/no_index'
55
+ app.middleware.insert_before Rack::Runtime, Spree::Api::Middleware::NoIndex
56
+ end
57
+
52
58
  # The resources agents may read and write are derived from the admin
53
59
  # controllers themselves (see Spree::Api::AgentResourceMap). Marked
54
60
  # stale on every reload rather than rebuilt: deriving it eager-loads the
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Api
3
+ module Middleware
4
+ # Marks every API response as not for search engines. Store API JSON is
5
+ # public and gets indexed once a crawler finds a link to it, where it
6
+ # competes with the storefront's own pages; tokened URLs such as digital
7
+ # downloads travel in emails, which is exactly how crawlers find them.
8
+ #
9
+ # A middleware rather than a controller callback: it also covers the
10
+ # OAuth and webhook controllers that do not inherit from the API base
11
+ # controller, and the 404 Rails renders for an unknown API path. Scoped
12
+ # by path because the same application may serve pages that must stay
13
+ # indexable.
14
+ class NoIndex
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def call(env)
20
+ status, headers, body = @app.call(env)
21
+ headers['x-robots-tag'] = 'noindex, nofollow' if api_request?(env)
22
+ [status, headers, body]
23
+ end
24
+
25
+ private
26
+
27
+ def api_request?(env)
28
+ env['PATH_INFO']&.start_with?('/api/')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -329,6 +329,7 @@ module Spree
329
329
  # team list does not publish about a colleague.
330
330
  user: { '$ref' => '#/components/schemas/Account' },
331
331
  sellers: { type: :array, items: { '$ref' => '#/components/schemas/SellerMeSummary' } },
332
+ store: { '$ref' => '#/components/schemas/Store' },
332
333
  permissions: { type: :array, items: { '$ref' => '#/components/schemas/PermissionRule' } },
333
334
  permission_keys: {
334
335
  type: :array,
@@ -337,7 +338,7 @@ module Spree
337
338
  example: %w[read_products write_products]
338
339
  }
339
340
  },
340
- required: %w[user sellers permissions permission_keys]
341
+ required: %w[user sellers store permissions permission_keys]
341
342
  },
342
343
  OnboardingProgress: {
343
344
  type: :object,
@@ -178,6 +178,24 @@ shared_examples 'returns 422 Unprocessable Entity' do
178
178
  end
179
179
  end
180
180
 
181
+ # Prosopite reports a statement only once it repeats, and most specs create a
182
+ # single record, so a list endpoint's N+1 stays invisible unless the spec
183
+ # renders several rows. Include this in every `GET #index` block with a
184
+ # `subject` that performs the request and a `let!` creating at least two
185
+ # records the serializer will render:
186
+ #
187
+ # it_behaves_like 'a list without N+1 queries' do
188
+ # let!(:records) { create_list(:product, 3, store: store) }
189
+ # end
190
+ shared_examples 'a list without N+1 queries' do
191
+ it 'renders several records without repeating a query' do
192
+ subject
193
+
194
+ expect(response).to have_http_status(:ok)
195
+ expect(json_response['data'].size).to be >= 2
196
+ end
197
+ end
198
+
181
199
  shared_examples 'requires API key' do
182
200
  context 'without API key' do
183
201
  let(:headers) { {} }
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: 6.0.0.rc5
4
+ version: 6.0.0.rc6
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-10-22 00:00:00.000000000 Z
11
+ date: 2026-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rswag-specs
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 6.0.0.rc5
47
+ version: 6.0.0.rc6
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 6.0.0.rc5
54
+ version: 6.0.0.rc6
55
55
  description: Spree Commerce Store / Admin API and HTTP webhooks
56
56
  email:
57
57
  - hello@spreecommerce.org
@@ -68,6 +68,7 @@ files:
68
68
  - app/controllers/concerns/spree/api/v3/admin/product_listing.rb
69
69
  - app/controllers/concerns/spree/api/v3/admin/product_membership.rb
70
70
  - app/controllers/concerns/spree/api/v3/admin/receivable_actions.rb
71
+ - app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb
71
72
  - app/controllers/concerns/spree/api/v3/admin/reporting_authorization.rb
72
73
  - app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb
73
74
  - app/controllers/concerns/spree/api/v3/admin/stock_receipt_actions.rb
@@ -83,6 +84,7 @@ files:
83
84
  - app/controllers/concerns/spree/api/v3/channel_resolution.rb
84
85
  - app/controllers/concerns/spree/api/v3/company_address_writes.rb
85
86
  - app/controllers/concerns/spree/api/v3/current_password_confirmation.rb
87
+ - app/controllers/concerns/spree/api/v3/digital_assets_resource.rb
86
88
  - app/controllers/concerns/spree/api/v3/error_handler.rb
87
89
  - app/controllers/concerns/spree/api/v3/file_uploads.rb
88
90
  - app/controllers/concerns/spree/api/v3/http_caching.rb
@@ -315,6 +317,7 @@ files:
315
317
  - app/controllers/spree/api/v3/seller/payouts_controller.rb
316
318
  - app/controllers/spree/api/v3/seller/policies_controller.rb
317
319
  - app/controllers/spree/api/v3/seller/product_types_controller.rb
320
+ - app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb
318
321
  - app/controllers/spree/api/v3/seller/products_controller.rb
319
322
  - app/controllers/spree/api/v3/seller/profile_controller.rb
320
323
  - app/controllers/spree/api/v3/seller/reasons_controller.rb
@@ -411,6 +414,7 @@ files:
411
414
  - app/request_schemas/spree/api/v3/admin/tax_identifier_request_schema.rb
412
415
  - app/request_schemas/spree/api/v3/admin/variant_request_schema.rb
413
416
  - app/request_schemas/spree/api/v3/custom_field_value_request_schema.rb
417
+ - app/request_schemas/spree/api/v3/digital_asset_request_schema.rb
414
418
  - app/request_schemas/spree/api/v3/file_upload_request_schema.rb
415
419
  - app/request_schemas/spree/api/v3/media_request_schema.rb
416
420
  - app/request_schemas/spree/api/v3/price_request_schema.rb
@@ -422,6 +426,7 @@ files:
422
426
  - app/request_schemas/spree/api/v3/variant_option_request_schema.rb
423
427
  - app/request_schemas/spree/api/v3/variant_request_schema.rb
424
428
  - app/serializers/concerns/spree/api/v3/admin/translatable.rb
429
+ - app/serializers/concerns/spree/api/v3/digital_asset_details.rb
425
430
  - app/serializers/concerns/spree/api/v3/purchase_summary.rb
426
431
  - app/serializers/spree/api/v3/address_owner_serializer.rb
427
432
  - app/serializers/spree/api/v3/address_serializer.rb
@@ -678,6 +683,7 @@ files:
678
683
  - app/serializers/spree/api/v3/seller/delivery_rate_serializer.rb
679
684
  - app/serializers/spree/api/v3/seller/delivery_serializer.rb
680
685
  - app/serializers/spree/api/v3/seller/delivery_zone_serializer.rb
686
+ - app/serializers/spree/api/v3/seller/digital_asset_serializer.rb
681
687
  - app/serializers/spree/api/v3/seller/exchange_line_item_serializer.rb
682
688
  - app/serializers/spree/api/v3/seller/exchange_serializer.rb
683
689
  - app/serializers/spree/api/v3/seller/export_serializer.rb
@@ -713,6 +719,7 @@ files:
713
719
  - app/serializers/spree/api/v3/seller/state_serializer.rb
714
720
  - app/serializers/spree/api/v3/seller/stock_level_serializer.rb
715
721
  - app/serializers/spree/api/v3/seller/stock_location_serializer.rb
722
+ - app/serializers/spree/api/v3/seller/store_serializer.rb
716
723
  - app/serializers/spree/api/v3/seller/tax_identifier_serializer.rb
717
724
  - app/serializers/spree/api/v3/seller/team_member_serializer.rb
718
725
  - app/serializers/spree/api/v3/seller/transfer_serializer.rb
@@ -800,6 +807,7 @@ files:
800
807
  - lib/spree/api/engine.rb
801
808
  - lib/spree/api/enum_types.rb
802
809
  - lib/spree/api/extension_fields.rb
810
+ - lib/spree/api/middleware/no_index.rb
803
811
  - lib/spree/api/middleware/request_size_limit.rb
804
812
  - lib/spree/api/oauth.rb
805
813
  - lib/spree/api/openapi/filter_documentation.rb
@@ -823,9 +831,9 @@ licenses:
823
831
  - BSD-3-Clause
824
832
  metadata:
825
833
  bug_tracker_uri: https://github.com/spree/spree/issues
826
- changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.rc5
834
+ changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.rc6
827
835
  documentation_uri: https://docs.spreecommerce.org/
828
- source_code_uri: https://github.com/spree/spree/tree/v6.0.0.rc5
836
+ source_code_uri: https://github.com/spree/spree/tree/v6.0.0.rc6
829
837
  post_install_message:
830
838
  rdoc_options: []
831
839
  require_paths: