spree_api 6.0.3 → 6.0.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/idempotent.rb +10 -1
  3. data/app/controllers/concerns/spree/api/v3/security_headers.rb +3 -0
  4. data/app/controllers/spree/api/v3/admin/base_controller.rb +31 -0
  5. data/app/controllers/spree/api/v3/admin/companies/memberships_controller.rb +21 -4
  6. data/app/controllers/spree/api/v3/admin/diagnostics_controller.rb +46 -0
  7. data/app/controllers/spree/api/v3/admin/me_controller.rb +0 -13
  8. data/app/controllers/spree/api/v3/admin/media_controller.rb +10 -0
  9. data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +6 -2
  10. data/app/controllers/spree/api/v3/admin/sellers/invitations_controller.rb +39 -3
  11. data/app/controllers/spree/api/v3/admin/sellers/team_controller.rb +1 -3
  12. data/app/controllers/spree/api/v3/admin/sellers_controller.rb +24 -1
  13. data/app/controllers/spree/api/v3/admin/stock_levels_controller.rb +15 -1
  14. data/app/controllers/spree/api/v3/admin/updates_controller.rb +4 -15
  15. data/app/controllers/spree/api/v3/seller/products/media_controller.rb +46 -0
  16. data/app/controllers/spree/api/v3/seller/products_controller.rb +4 -2
  17. data/app/controllers/spree/api/v3/seller/profile_controller.rb +13 -5
  18. data/app/controllers/spree/api/v3/store/carts_controller.rb +10 -7
  19. data/app/serializers/spree/api/v3/admin/company_membership_serializer.rb +4 -0
  20. data/app/serializers/spree/api/v3/admin/diagnostics_serializer.rb +30 -0
  21. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +6 -1
  22. data/app/serializers/spree/api/v3/admin/seller_serializer.rb +10 -0
  23. data/app/serializers/spree/api/v3/product_category_serializer.rb +26 -0
  24. data/app/serializers/spree/api/v3/product_collection_serializer.rb +26 -0
  25. data/app/serializers/spree/api/v3/seller/order_line_item_serializer.rb +2 -2
  26. data/config/routes.rb +3 -1
  27. data/lib/spree/api/agent_resource_map.rb +42 -7
  28. data/lib/spree/api/dependencies.rb +1 -1
  29. data/lib/spree/api/testing_support/openapi.rb +2 -1
  30. data/lib/spree/api/testing_support/sdk_method.rb +34 -0
  31. data/openapi/admin.yaml +695 -279
  32. data/openapi/store.yaml +125 -125
  33. metadata +12 -6
@@ -55,6 +55,11 @@ module Spree
55
55
  # so the minimum-products requirement asks the same question free.
56
56
  attributes :products_count
57
57
 
58
+ # What is live and what waits on the operator's review, read in the
59
+ # same query as the total above.
60
+ typelize(Spree::Product::ALL_STATUSES.to_h { |status| [status.to_sym, :number] })
61
+ attribute :products_count_by_status, &:products_count_by_status
62
+
58
63
  attributes :users_count
59
64
 
60
65
  # How far through the marketplace's checklist this seller is — what
@@ -78,6 +83,11 @@ module Spree
78
83
  resource: proc { Spree.api.admin_address_serializer },
79
84
  if: proc { |seller| seller.returns_address.present? }
80
85
 
86
+ # The stock location the returns address is read from. The address
87
+ # is not the seller's own to write — it is edited on this location.
88
+ typelize returns_location_id: [:string, nullable: true]
89
+ prefixed_id_attributes :returns_location
90
+
81
91
  expandable_custom_fields admin: true
82
92
  end
83
93
  end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductCategorySerializer < BaseSerializer
5
+ typelize product_id: [:string, nullable: true],
6
+ category_id: [:string, nullable: true]
7
+
8
+ attribute :product_id do |product_category|
9
+ prefixed_id_for(Spree::Product, product_category.product_id)
10
+ end
11
+
12
+ attribute :category_id do |product_category|
13
+ prefixed_id_for(Spree::Category, product_category.category_id)
14
+ end
15
+
16
+ private
17
+
18
+ def prefixed_id_for(klass, id)
19
+ return nil if id.nil?
20
+
21
+ "#{klass._prefix_id_prefix}_#{Spree::PrefixedId::SQIDS.encode([id])}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ class ProductCollectionSerializer < BaseSerializer
5
+ typelize product_id: [:string, nullable: true],
6
+ collection_id: [:string, nullable: true]
7
+
8
+ attribute :product_id do |product_collection|
9
+ prefixed_id_for(Spree::Product, product_collection.product_id)
10
+ end
11
+
12
+ attribute :collection_id do |product_collection|
13
+ prefixed_id_for(Spree::Collection, product_collection.collection_id)
14
+ end
15
+
16
+ private
17
+
18
+ def prefixed_id_for(klass, id)
19
+ return nil if id.nil?
20
+
21
+ "#{klass._prefix_id_prefix}_#{Spree::PrefixedId::SQIDS.encode([id])}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -13,7 +13,7 @@ module Spree
13
13
  class OrderLineItemSerializer < V3::BaseSerializer
14
14
  typelize name: :string, sku: [:string, nullable: true], options_text: [:string, nullable: true],
15
15
  quantity: :number, currency: [:string, enum: Spree::Money::Rounding::CURRENCY_CODES, enum_type_name: 'CurrencyCode'], variant_id: [:string, nullable: true],
16
- thumbnail_url: [:string, nullable: true]
16
+ thumbnail_url: [:string, nullable: true], product_id: [:string, nullable: true]
17
17
 
18
18
  attributes :name, :options_text, :quantity, :currency
19
19
 
@@ -22,7 +22,7 @@ module Spree
22
22
  money_attributes :discounted_amount,
23
23
  :additional_tax_total, :total
24
24
 
25
- prefixed_id_attributes :variant
25
+ prefixed_id_attributes :variant, :product
26
26
 
27
27
  # How a seller finds the item on their own shelf.
28
28
  attribute :sku do |line_item|
data/config/routes.rb CHANGED
@@ -285,6 +285,7 @@ Spree::Core::Engine.add_routes do
285
285
  patch 'me', to: 'me#update'
286
286
 
287
287
  resource :updates, only: :show
288
+ resource :diagnostics, only: :show
288
289
 
289
290
  # Store Settings
290
291
  resource :store, only: [:show, :update], controller: 'store' do
@@ -610,7 +611,7 @@ Spree::Core::Engine.add_routes do
610
611
  # The operator is the only one who can repair a seller whose team
611
612
  # has locked itself out, which the seller's own panel cannot do.
612
613
  resources :team, only: [:index, :destroy], controller: 'sellers/team'
613
- resources :invitations, only: [:index, :destroy], controller: 'sellers/invitations' do
614
+ resources :invitations, only: [:index, :create, :destroy], controller: 'sellers/invitations' do
614
615
  patch :resend, on: :member
615
616
  resource :acceptance_link, only: [:show], controller: 'sellers/invitations/acceptance_links'
616
617
  end
@@ -930,6 +931,7 @@ Spree::Core::Engine.add_routes do
930
931
 
931
932
  resources :digital_assets, controller: 'products/digital_assets',
932
933
  only: [:index, :create, :show, :update, :destroy]
934
+ resources :media, controller: 'products/media', only: [:destroy]
933
935
  end
934
936
 
935
937
 
@@ -71,16 +71,23 @@ module Spree
71
71
  # one, because the scope belongs to the resource being imported or
72
72
  # exported — creating a product import is a product write, and reading
73
73
  # its rows exposes the uploaded data. A single static key cannot say
74
- # that, so these are registered read-only under the broadest key the
75
- # controller can demand, and the write path stays with the endpoint.
74
+ # that, so these open to a caller holding the scope of any one type,
75
+ # in the kind the controller asks for: `read_` for an export, `write_`
76
+ # for an import. The endpoint then keeps to the types that key covers.
77
+ #
78
+ # Creating one stays with create_import and create_export; reading,
79
+ # listing and deleting go to the endpoint.
76
80
  DYNAMIC_SCOPE_RESOURCES = {
77
- 'imports' => { model_name: 'Spree::Import', permission: 'read_settings',
81
+ 'imports' => { model_name: 'Spree::Import', access_kind: 'write',
78
82
  serializer_name: 'Spree::Api::V3::Admin::ImportSerializer',
79
83
  dashboard_path: '/settings/imports' },
80
- 'exports' => { model_name: 'Spree::Export', permission: 'read_settings',
84
+ 'exports' => { model_name: 'Spree::Export', access_kind: 'read',
81
85
  serializer_name: 'Spree::Api::V3::Admin::ExportSerializer' }
82
86
  }.freeze
83
87
 
88
+ # The operations the generic tools reach on those resources.
89
+ DYNAMIC_SCOPE_ACTIONS = %i[index show destroy].freeze
90
+
84
91
  class << self
85
92
  # Registers this walk as how the core map fills itself, and marks the
86
93
  # map stale so the next reader gets a fresh one. Called on every code
@@ -102,7 +109,11 @@ module Spree
102
109
  registered = resolve_collisions(controllers.filter_map { |controller| derive(controller) })
103
110
  registered.each { |entry| map.register(**entry) }
104
111
 
105
- DYNAMIC_SCOPE_RESOURCES.each { |key, attributes| map.register(key: key, **attributes) }
112
+ DYNAMIC_SCOPE_RESOURCES.each do |key, attributes|
113
+ map.register(key: key, permission: nil, **attributes.except(:access_kind),
114
+ api_paths: dynamic_scope_paths(attributes[:model_name]),
115
+ access_permissions: type_permissions(attributes[:model_name], attributes[:access_kind]))
116
+ end
106
117
 
107
118
  registered.size + DYNAMIC_SCOPE_RESOURCES.size
108
119
  end
@@ -183,6 +194,7 @@ module Spree
183
194
  create_workflow = workflow_key(instance, :create_workflow)
184
195
  update_workflow = workflow_key(instance, :update_workflow)
185
196
  writable = AgentWriteSchemas.attribute_names(key)
197
+ api_paths = api_paths_for(controller)
186
198
 
187
199
  {
188
200
  key: key,
@@ -190,7 +202,8 @@ module Spree
190
202
  permission: "read_#{scope}",
191
203
  serializer_name: serializer.name,
192
204
  dashboard_path: DASHBOARD_PATHS[key],
193
- api_paths: api_paths_for(controller),
205
+ api_paths: api_paths,
206
+ index_paths: Array(api_paths[:index]),
194
207
  write_permission: write_permission_for(model, scope, create_workflow, update_workflow, writable, controller),
195
208
  writable_attributes: writable,
196
209
  create_workflow_key: create_workflow,
@@ -219,6 +232,25 @@ module Spree
219
232
 
220
233
  private
221
234
 
235
+ # The read and delete paths of the controller serving an import or
236
+ # export. Without them the generic tools refused every one, though
237
+ # create_export tells the agent to read the export with get_resource.
238
+ def dynamic_scope_paths(model_name)
239
+ controller = controllers.find { |candidate| model_for(candidate)&.name == model_name }
240
+ return {} if controller.nil?
241
+
242
+ api_paths_for(controller).slice(*DYNAMIC_SCOPE_ACTIONS)
243
+ end
244
+
245
+ # The catalog keys that open an import or export of each registered
246
+ # type. A type with no scope of its own needs `read_all` or
247
+ # `write_all`, which no catalog key stands for, so it adds none.
248
+ def type_permissions(model_name, kind)
249
+ model_name.constantize.available_types.
250
+ filter_map { |type| type.required_scope.presence && "#{kind}_#{type.required_scope}" }.
251
+ uniq.select { |key| Spree.permissions.key?(key) }
252
+ end
253
+
222
254
  # What the controller does with its own collection. A controller that
223
255
  # disables DISTINCT has a reason — Postgres refuses it beside an
224
256
  # ORDER BY the select list does not carry — and a search tool adding
@@ -273,7 +305,10 @@ module Spree
273
305
  # nothing about depth — so a resource served both at the top
274
306
  # level and under a parent kept whichever won on scope, and a
275
307
  # tool was handed a path with an unfillable `:parent_id` in it.
276
- api_paths: shallowest_paths(candidates)
308
+ api_paths: shallowest_paths(candidates),
309
+ # Every collection path, not only the shallowest, so a search
310
+ # under a parent reaches the controller that lists its records.
311
+ index_paths: candidates.flat_map { |entry| entry[:index_paths] }.uniq
277
312
  )
278
313
  end
279
314
  end
@@ -38,7 +38,7 @@ module Spree
38
38
  channel order_routing_rule product_publication market delivery_method delivery_method_service
39
39
  stock_location stock_reservation delivery_rate freight_summary freight_summary_line payment_method
40
40
  credit_card store_credit store_credit_event customer_group payment_source digital_asset digital_link
41
- store setup_task update_status api_key oauth_application oauth_access_token oauth_consent_request oauth_redirect
41
+ store setup_task update_status diagnostics api_key oauth_application oauth_access_token oauth_consent_request oauth_redirect
42
42
  oauth_protected_resource allowed_origin webhook_endpoint webhook_event webhook_delivery invitation
43
43
  invitation_acceptance_link role permission export saved_report import import_row import_mapping promotion
44
44
  maintenance_task_run
@@ -2,6 +2,7 @@ require 'spree/api/testing_support/v3/base'
2
2
  require 'spree/api/testing_support/agent_tools'
3
3
  require 'spree/api/testing_support/deterministic_openapi'
4
4
  require 'spree/api/testing_support/filter_parameters'
5
+ require 'spree/api/testing_support/sdk_method'
5
6
 
6
7
  module Spree
7
8
  module Api
@@ -165,7 +166,7 @@ module Spree
165
166
  # and in its Rakefile, `require 'spree/api/testing_support/openapi_tasks'`
166
167
  # for `rake spree:openapi:generate`.
167
168
  module Openapi
168
- HELPERS = [RequestBody, FilterParameters, AdminScope, AdminOperation, AgentTools].freeze
169
+ HELPERS = [RequestBody, FilterParameters, AdminScope, AdminOperation, AgentTools, SdkMethod].freeze
169
170
 
170
171
  class << self
171
172
  # @param config [RSpec::Core::Configuration]
@@ -0,0 +1,34 @@
1
+ module Spree
2
+ module Api
3
+ module TestingSupport
4
+ # rswag helper for what an operation is called in the SDK client
5
+ # `spree sdk generate` writes for an extension. By default a method is
6
+ # named after the path (`PATCH /quotes/{id}/send` → `quotes.send`), and
7
+ # an `operationId` renames it, but the `operationId` also names the
8
+ # operation's agent tool. This names the SDK method alone:
9
+ #
10
+ # delete 'Remove a file from a quote' do
11
+ # sdk_method :remove_attachment # quotes.removeAttachment
12
+ # end
13
+ #
14
+ # post 'Upload a file for a quote request' do
15
+ # sdk_method :presign_attachment, namespace: :quotes # not carts.…
16
+ # end
17
+ #
18
+ # See docs/plans/6.0-sdk-extensions.md.
19
+ module SdkMethod
20
+ METHOD_KEY = :'x-spree-sdk-method'
21
+ NAMESPACE_KEY = :'x-spree-sdk-namespace'
22
+
23
+ # @param name [Symbol, String] the method, snake_case or camelCase
24
+ # @param namespace [Symbol, String, nil] the client namespace it goes in,
25
+ # instead of the path's resource
26
+ # @return [void]
27
+ def sdk_method(name, namespace: nil)
28
+ metadata[:operation][METHOD_KEY] = name.to_s
29
+ metadata[:operation][NAMESPACE_KEY] = namespace.to_s if namespace
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end