spree_core 6.0.2 → 6.0.3
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 +4 -4
- data/app/jobs/spree/addresses/geocode_address_job.rb +6 -3
- data/app/jobs/spree/variants/remove_line_item_job.rb +5 -0
- data/app/models/concerns/spree/preference_schema.rb +5 -0
- data/app/models/concerns/spree/typed_associations.rb +53 -20
- data/app/models/spree/address.rb +2 -2
- data/app/models/spree/collection.rb +7 -2
- data/app/models/spree/order.rb +1 -1
- data/app/models/spree/product.rb +15 -5
- data/app/models/spree/promotion.rb +1 -2
- data/app/services/spree/newsletter/subscribe.rb +1 -1
- data/app/services/spree/orders/create_from_cart.rb +6 -2
- data/app/tools/spree/agent_tools/create_export.rb +3 -1
- data/app/tools/spree/agent_tools/create_import.rb +1 -0
- data/app/tools/spree/agent_tools/create_media.rb +2 -1
- data/app/tools/spree/agent_tools/create_resource.rb +12 -4
- data/app/tools/spree/agent_tools/delete_resource.rb +12 -4
- data/app/tools/spree/agent_tools/describe_resource.rb +69 -6
- data/app/tools/spree/agent_tools/get_resource.rb +7 -4
- data/app/tools/spree/agent_tools/resource_write.rb +15 -2
- data/app/tools/spree/agent_tools/search_resources.rb +10 -11
- data/app/tools/spree/agent_tools/update_resource.rb +6 -6
- data/app/tools/spree/agent_tools/upload_file.rb +1 -0
- data/app/workflows/spree/carts/complete.rb +4 -1
- data/app/workflows/spree/carts/merge.rb +25 -4
- data/app/workflows/spree/orders/complete.rb +39 -14
- data/app/workflows/spree/products/activate.rb +7 -2
- data/app/workflows/spree/products/archive.rb +7 -2
- data/app/workflows/spree/products/draft.rb +7 -2
- data/app/workflows/spree/products/status_transition.rb +24 -0
- data/config/locales/en.yml +3 -0
- data/lib/generators/spree/api_resource/api_resource_generator.rb +35 -3
- data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +9 -4
- data/lib/generators/spree/api_resource/templates/admin_integration_spec.rb.tt +74 -0
- data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +11 -6
- data/lib/spree/agent_tool.rb +105 -1
- data/lib/spree/agent_tools/default_catalog.rb +16 -21
- data/lib/spree/agent_tools/legacy_tools.rb +212 -0
- data/lib/spree/agent_tools/operation_catalog.rb +206 -0
- data/lib/spree/agent_tools/operation_tool.rb +189 -0
- data/lib/spree/agent_tools/registry.rb +65 -31
- data/lib/spree/agent_tools/resource_map.rb +128 -5
- data/lib/spree/agent_tools.rb +3 -3
- data/lib/spree/core/dependencies_helper.rb +96 -40
- data/lib/spree/core/engine.rb +2 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +19 -2
- data/lib/spree/testing_support/extension_rake.rb +12 -0
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +34 -0
- data/lib/tasks/dependencies.rake +2 -2
- metadata +23 -7
- data/lib/spree/agent_tools/workflow_endpoints.rb +0 -88
- data/lib/spree/agent_tools/workflow_schema.rb +0 -279
- data/lib/spree/agent_tools/workflow_tool.rb +0 -654
|
@@ -14,19 +14,19 @@ module Spree
|
|
|
14
14
|
param :resource, description: 'Resource key — call describe_resource for the list', required: true
|
|
15
15
|
param :id, description: 'The record id as shown in search results', required: true
|
|
16
16
|
param :attributes, type: :object, description: 'Attributes to change', required: true
|
|
17
|
+
param :parent_id, description: PARENT_ID_DESCRIPTION
|
|
17
18
|
|
|
18
|
-
def call(resource:, id:, attributes: {})
|
|
19
|
+
def call(resource:, id:, attributes: {}, parent_id: nil)
|
|
19
20
|
entry, refusal = writable_entry(resource, :update)
|
|
20
21
|
return refusal if refusal
|
|
21
22
|
|
|
22
23
|
body, rejection = body_for(entry, attributes)
|
|
23
24
|
return rejection if rejection
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
26
|
+
path, missing = write_path(entry, :update, id: id, parent_id: parent_id)
|
|
27
|
+
return missing if missing
|
|
28
|
+
|
|
29
|
+
response = dispatch.call(method: :patch, path: path, body: body)
|
|
30
30
|
|
|
31
31
|
result_for(response, entry, summary: summary(resource: resource, id: id, attributes: attributes))
|
|
32
32
|
end
|
|
@@ -21,6 +21,7 @@ module Spree
|
|
|
21
21
|
# passes; whatever uses the file checks its own permission after.
|
|
22
22
|
permission 'write_products'
|
|
23
23
|
mutating!
|
|
24
|
+
additive!
|
|
24
25
|
|
|
25
26
|
param :filename, description: 'The file name, with its extension', required: true
|
|
26
27
|
param :content, description: 'The file contents — text as text, binary base64 encoded',
|
|
@@ -292,6 +292,7 @@ module Spree
|
|
|
292
292
|
copy_tax_exemptions!(cart, order, line_item_map, fulfillment_map, fee_map)
|
|
293
293
|
copy_po_document!(cart, order)
|
|
294
294
|
repoint_money_records!(cart, order)
|
|
295
|
+
Spree.cart_copiers.each { |copier| copier.call(cart: cart, order: order, line_item_map: line_item_map) }
|
|
295
296
|
|
|
296
297
|
order.update_columns(
|
|
297
298
|
item_total: cart.item_total,
|
|
@@ -560,7 +561,9 @@ module Spree
|
|
|
560
561
|
# its children is the order this checkout carries on with. No child holds
|
|
561
562
|
# the confirmation: the customer is confirmed from the group.
|
|
562
563
|
def complete_orders
|
|
563
|
-
|
|
564
|
+
# carts.complete.validate ran before the payment was taken; a veto now
|
|
565
|
+
# would leave the customer charged for an order that never places.
|
|
566
|
+
result = Spree.order_complete_workflow.call(order: order, payment_pending: payment_pending, validated: true)
|
|
564
567
|
failure(cart, code: 'completion_failed', message: result.error) if result.failure?
|
|
565
568
|
|
|
566
569
|
return unless result.value.is_a?(Spree::OrderGroup)
|
|
@@ -10,7 +10,14 @@ module Spree
|
|
|
10
10
|
# cap the merged quantity) and previously required replacing the whole
|
|
11
11
|
# strategy class.
|
|
12
12
|
class Merge < Spree::Workflow
|
|
13
|
-
hooks :validate, :after_merge
|
|
13
|
+
hooks :validate, :after_items_moved, :after_merge
|
|
14
|
+
|
|
15
|
+
# Every line item of the folded-in cart → the line item now holding its
|
|
16
|
+
# quantity: itself when it moved across, or the surviving cart's line it
|
|
17
|
+
# was added to. Set before +after_items_moved+ runs.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash{Spree::LineItem => Spree::LineItem}]
|
|
20
|
+
attr_reader :line_item_map
|
|
14
21
|
|
|
15
22
|
# @param cart [Spree::Cart] the surviving cart
|
|
16
23
|
# @param other_cart [Spree::Cart, nil] the cart folded in and destroyed
|
|
@@ -28,6 +35,10 @@ module Spree
|
|
|
28
35
|
|
|
29
36
|
ApplicationRecord.transaction do
|
|
30
37
|
step :move_line_items
|
|
38
|
+
# Inside the transaction and before the absorbed lines are deleted,
|
|
39
|
+
# so an extension can move what it keeps against them.
|
|
40
|
+
run_hooks :after_items_moved
|
|
41
|
+
step :destroy_absorbed_line_items
|
|
31
42
|
step :associate_customer
|
|
32
43
|
step :destroy_drained_cart
|
|
33
44
|
end
|
|
@@ -46,19 +57,29 @@ module Spree
|
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
# A matching item absorbs the quantity; everything else is re-pointed
|
|
49
|
-
# at the surviving cart.
|
|
60
|
+
# at the surviving cart. Absorbed items are deleted only after the
|
|
61
|
+
# +after_items_moved+ hook.
|
|
50
62
|
def move_line_items
|
|
51
|
-
other_cart.line_items.reload.
|
|
63
|
+
@line_item_map = other_cart.line_items.reload.to_a.index_with do |other_line_item|
|
|
52
64
|
existing = matching_line_item(other_line_item)
|
|
53
65
|
|
|
54
66
|
if existing
|
|
55
67
|
existing.quantity += other_line_item.quantity
|
|
56
68
|
existing.save!
|
|
57
|
-
|
|
69
|
+
existing
|
|
58
70
|
else
|
|
59
71
|
other_line_item.update!(cart: cart, order: nil)
|
|
72
|
+
other_line_item
|
|
60
73
|
end
|
|
61
74
|
end
|
|
75
|
+
# Matching loaded the surviving cart's items before any moved in.
|
|
76
|
+
cart.line_items.reset
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def destroy_absorbed_line_items
|
|
80
|
+
line_item_map.each do |other_line_item, line_item|
|
|
81
|
+
other_line_item.destroy! unless other_line_item == line_item
|
|
82
|
+
end
|
|
62
83
|
end
|
|
63
84
|
|
|
64
85
|
# Options-aware comparison (personalization, gift wrapping) with a
|
|
@@ -7,6 +7,8 @@ module Spree
|
|
|
7
7
|
# may still need to process payments). Idempotent: an already-placed
|
|
8
8
|
# order halts successfully, so interrupted completions replay safely.
|
|
9
9
|
class Complete < Spree::Workflow
|
|
10
|
+
hooks :validate
|
|
11
|
+
|
|
10
12
|
# Set only when the order divided between sellers.
|
|
11
13
|
attr_reader :order_group
|
|
12
14
|
|
|
@@ -18,9 +20,13 @@ module Spree
|
|
|
18
20
|
# rollup work's call (docs/plans/6.0-b2b-wholesale-shipping.md phase 7)
|
|
19
21
|
# @param notify_customer [Boolean, nil] admin drafts pass false to
|
|
20
22
|
# complete silently; nil (checkout) leaves customer notification on
|
|
23
|
+
# @param validated [Boolean] the caller already ran its own veto, so the
|
|
24
|
+
# validate hook is skipped: checkout's finalize, whose
|
|
25
|
+
# carts.complete.validate ran before the payment was taken, and the
|
|
26
|
+
# orders a seller division places beside the one it came from
|
|
21
27
|
# @return [Spree::ServiceModule::Result] value is the order, or the
|
|
22
28
|
# Spree::OrderGroup when it divided between sellers
|
|
23
|
-
def perform(order:, payment_pending: false, notify_customer: nil)
|
|
29
|
+
def perform(order:, payment_pending: false, notify_customer: nil, validated: false)
|
|
24
30
|
super
|
|
25
31
|
|
|
26
32
|
order.notify_customer = notify_customer unless notify_customer.nil?
|
|
@@ -37,20 +43,30 @@ module Spree
|
|
|
37
43
|
|
|
38
44
|
step :ensure_not_canceled
|
|
39
45
|
|
|
40
|
-
order.with_lock do
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
placed_here = order.with_lock do
|
|
47
|
+
# A completion running alongside this one placed it while this one
|
|
48
|
+
# waited for the lock. Placed, not merely in that status: it may
|
|
49
|
+
# have been canceled since.
|
|
50
|
+
next false if order.reload.completed?
|
|
51
|
+
|
|
52
|
+
step :validate_order unless validated
|
|
53
|
+
step :process_payments unless payment_pending || !order.payment_required?
|
|
54
|
+
step :split_by_seller
|
|
55
|
+
step :finalize_fulfillments
|
|
56
|
+
step :place_order
|
|
57
|
+
step :freeze_tax_exemptions
|
|
58
|
+
step :use_coupon_codes
|
|
59
|
+
step :redeem_gift_card
|
|
60
|
+
step :fulfill_auto_fulfillments
|
|
61
|
+
step :ensure_digital_links
|
|
62
|
+
true
|
|
52
63
|
end
|
|
53
64
|
|
|
65
|
+
# The run that placed it announces it and finishes what follows; this
|
|
66
|
+
# one answers as a replay would, so nothing after placement happens
|
|
67
|
+
# twice.
|
|
68
|
+
halt!(order.order_group || order) unless placed_here
|
|
69
|
+
|
|
54
70
|
step :ensure_placed_status
|
|
55
71
|
step :release_stock_reservations
|
|
56
72
|
step :cancel_unused_payment_sessions
|
|
@@ -67,6 +83,14 @@ module Spree
|
|
|
67
83
|
failure(order, 'Order is canceled') if order.canceled?
|
|
68
84
|
end
|
|
69
85
|
|
|
86
|
+
# The extension veto, under the order's row lock: a handler sees the
|
|
87
|
+
# order as it is placed, and anything else that takes the lock to change
|
|
88
|
+
# it waits for the outcome. Rejecting rolls the lock back before any
|
|
89
|
+
# payment is processed.
|
|
90
|
+
def validate_order
|
|
91
|
+
run_hooks :validate
|
|
92
|
+
end
|
|
93
|
+
|
|
70
94
|
def process_payments
|
|
71
95
|
order.process_payments! unless payment_covered?
|
|
72
96
|
|
|
@@ -276,7 +300,8 @@ module Spree
|
|
|
276
300
|
end
|
|
277
301
|
|
|
278
302
|
def place_sibling(sibling)
|
|
279
|
-
result = Spree.order_complete_workflow.call(order: sibling, payment_pending: true, notify_customer: false
|
|
303
|
+
result = Spree.order_complete_workflow.call(order: sibling, payment_pending: true, notify_customer: false,
|
|
304
|
+
validated: true)
|
|
280
305
|
failure(order, result.error) if result.failure?
|
|
281
306
|
end
|
|
282
307
|
|
|
@@ -5,6 +5,8 @@ module Spree
|
|
|
5
5
|
# after_transition, so a handler can veto the change before anything is
|
|
6
6
|
# written and see the product it applies to.
|
|
7
7
|
class Activate < Spree::Workflow
|
|
8
|
+
include StatusTransition
|
|
9
|
+
|
|
8
10
|
hooks :validate, :after_activate
|
|
9
11
|
|
|
10
12
|
# @param product [Spree::Product]
|
|
@@ -14,12 +16,15 @@ module Spree
|
|
|
14
16
|
|
|
15
17
|
run_hooks :validate
|
|
16
18
|
|
|
17
|
-
ApplicationRecord.transaction do
|
|
19
|
+
moved = ApplicationRecord.transaction do
|
|
20
|
+
next false if already_in?('active')
|
|
21
|
+
|
|
18
22
|
step :mark_active
|
|
19
23
|
run_hooks :after_activate
|
|
24
|
+
true
|
|
20
25
|
end
|
|
21
26
|
|
|
22
|
-
product.publish_event('product.activated')
|
|
27
|
+
product.publish_event('product.activated') if moved
|
|
23
28
|
success(product)
|
|
24
29
|
end
|
|
25
30
|
|
|
@@ -3,6 +3,8 @@ module Spree
|
|
|
3
3
|
# Takes a product off sale for good. Replaces the `archive` state machine
|
|
4
4
|
# event.
|
|
5
5
|
class Archive < Spree::Workflow
|
|
6
|
+
include StatusTransition
|
|
7
|
+
|
|
6
8
|
hooks :validate, :after_archive
|
|
7
9
|
|
|
8
10
|
# @param product [Spree::Product]
|
|
@@ -12,13 +14,16 @@ module Spree
|
|
|
12
14
|
|
|
13
15
|
run_hooks :validate
|
|
14
16
|
|
|
15
|
-
ApplicationRecord.transaction do
|
|
17
|
+
moved = ApplicationRecord.transaction do
|
|
18
|
+
next false if already_in?('archived')
|
|
19
|
+
|
|
16
20
|
step :mark_archived
|
|
17
21
|
step :withdraw_submission
|
|
18
22
|
run_hooks :after_archive
|
|
23
|
+
true
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
product.publish_event('product.archived')
|
|
26
|
+
product.publish_event('product.archived') if moved
|
|
22
27
|
success(product)
|
|
23
28
|
end
|
|
24
29
|
|
|
@@ -3,6 +3,8 @@ module Spree
|
|
|
3
3
|
# Returns a product to draft, hiding it from the storefront while it is
|
|
4
4
|
# worked on. Replaces the `draft` state machine event.
|
|
5
5
|
class Draft < Spree::Workflow
|
|
6
|
+
include StatusTransition
|
|
7
|
+
|
|
6
8
|
hooks :validate, :after_draft
|
|
7
9
|
|
|
8
10
|
# @param product [Spree::Product]
|
|
@@ -12,17 +14,20 @@ module Spree
|
|
|
12
14
|
|
|
13
15
|
run_hooks :validate
|
|
14
16
|
|
|
15
|
-
ApplicationRecord.transaction do
|
|
17
|
+
moved = ApplicationRecord.transaction do
|
|
18
|
+
next false if already_in?('draft')
|
|
19
|
+
|
|
16
20
|
step :mark_draft
|
|
17
21
|
step :withdraw_submission
|
|
18
22
|
run_hooks :after_draft
|
|
23
|
+
true
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
# Its siblings (Activate, Archive, Propose, Approve, Reject) all
|
|
22
27
|
# announce themselves, and this one now carries a marketplace meaning
|
|
23
28
|
# too: taking a listing back withdraws an open submission, which a
|
|
24
29
|
# seller's integration has the same reason to hear about.
|
|
25
|
-
product.publish_event('product.drafted')
|
|
30
|
+
product.publish_event('product.drafted') if moved
|
|
26
31
|
|
|
27
32
|
success(product)
|
|
28
33
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Products
|
|
3
|
+
# What the status workflows share: whether the product is already in the
|
|
4
|
+
# status a move would put it in, read under a row lock inside the
|
|
5
|
+
# workflow's own transaction.
|
|
6
|
+
#
|
|
7
|
+
# Two requests for the same move otherwise both read the old status, both
|
|
8
|
+
# write the new one and both publish the event, so every webhook
|
|
9
|
+
# subscriber hears of one change twice. With the lock the second waits,
|
|
10
|
+
# finds the move made, and changes nothing.
|
|
11
|
+
#
|
|
12
|
+
# The status is read by query rather than by reloading the product, so a
|
|
13
|
+
# caller's unsaved edits on the record are kept.
|
|
14
|
+
module StatusTransition
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# @param status [String]
|
|
18
|
+
# @return [Boolean]
|
|
19
|
+
def already_in?(status)
|
|
20
|
+
product.class.where(id: product.id).lock.pick(:status) == status
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/config/locales/en.yml
CHANGED
|
@@ -900,6 +900,7 @@ en:
|
|
|
900
900
|
invalid_status: Invalid status
|
|
901
901
|
invalid_storefront_access: is not a valid storefront access level
|
|
902
902
|
items_not_a_list: items must be a list of objects
|
|
903
|
+
missing_row_type: 'is required (expected one of: %{types})'
|
|
903
904
|
money_preference_too_many_decimals: has more than %{count} decimal places for %{currency}
|
|
904
905
|
must_be_greater_than_min_amount: must be greater than or equal to the minimum
|
|
905
906
|
no_pickup_point_provider: this delivery method has no pickup point provider configured
|
|
@@ -909,6 +910,7 @@ en:
|
|
|
909
910
|
oidc_missing_code: The identity provider did not return an authorization code.
|
|
910
911
|
oidc_requires_redirect: This provider requires signing in through its own login page.
|
|
911
912
|
pickup_point_not_found: pickup point could not be found with this provider
|
|
913
|
+
product_in_review: A product awaiting review is approved or rejected, not moved to another status
|
|
912
914
|
product_not_in_store: must all belong to this store
|
|
913
915
|
profile_does_not_accept_provider: "%{provider} deliveries cannot join the %{profile} profile"
|
|
914
916
|
promotion_discount_not_editable: promotion discounts are managed by recalculation and cannot be edited
|
|
@@ -924,6 +926,7 @@ en:
|
|
|
924
926
|
store_is_already_set: Store is already set
|
|
925
927
|
unknown_calculator_type: "%{type} is not a calculator offered here"
|
|
926
928
|
unknown_custom_field_type: 'is not a known custom field type (expected one of: %{types})'
|
|
929
|
+
unknown_row_type: "%{type} is not a %{family} offered here (expected one of: %{types})"
|
|
927
930
|
unknown_scopes: 'contains unknown scopes: %{scopes}'
|
|
928
931
|
unregistered_actor: is not a registered actor class
|
|
929
932
|
variant_product_mismatch: must belong to the same product as the variant
|
|
@@ -169,6 +169,10 @@ module Spree
|
|
|
169
169
|
if options[:admin]
|
|
170
170
|
template 'admin_controller_spec.rb.tt',
|
|
171
171
|
"spec/controllers/spree/api/v3/admin/#{plural_name}_controller_spec.rb"
|
|
172
|
+
# Documents the endpoints in openapi/admin.yaml (`spree task
|
|
173
|
+
# openapi:generate`): the SDK request types and the agent tools come
|
|
174
|
+
# from there.
|
|
175
|
+
template 'admin_integration_spec.rb.tt', "spec/integration/admin/#{plural_name}_spec.rb"
|
|
172
176
|
end
|
|
173
177
|
end
|
|
174
178
|
|
|
@@ -297,6 +301,9 @@ module Spree
|
|
|
297
301
|
if options[:store] || options[:admin]
|
|
298
302
|
say " #{store_scoped? ? 5 : 4}. Run the specs: pnpm exec spree test spec/controllers/spree/api/v3/"
|
|
299
303
|
end
|
|
304
|
+
if options[:admin] && !options[:skip_specs]
|
|
305
|
+
say " #{store_scoped? ? 6 : 5}. Document the Admin API (agent tools, SDK types): pnpm exec spree task openapi:generate"
|
|
306
|
+
end
|
|
300
307
|
say ''
|
|
301
308
|
end
|
|
302
309
|
|
|
@@ -344,9 +351,26 @@ module Spree
|
|
|
344
351
|
# Attributes the controller permits on write. By default, every column
|
|
345
352
|
# except references (FKs come through nested), attachments, rich text.
|
|
346
353
|
def permitted_attribute_names
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
writable_attributes.map(&:name)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# @return [Array<Array(String, Symbol, Boolean)>] each writable attribute,
|
|
358
|
+
# its `request_params` type, and whether creating needs it — the model
|
|
359
|
+
# template validates the presence of every one but booleans
|
|
360
|
+
def request_param_attributes
|
|
361
|
+
writable_attributes.map { |a| [a.name, REQUEST_PARAM_TYPES.fetch(a.type, :string), a.type != :boolean] }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Decimal attributes travel as decimal strings (`'1.00'`), never JSON
|
|
365
|
+
# numbers; a money attribute sent as a number is refused.
|
|
366
|
+
#
|
|
367
|
+
# @return [Array<String>]
|
|
368
|
+
def decimal_attribute_names
|
|
369
|
+
writable_attributes.select { |a| a.type == :decimal }.map(&:name)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def writable_attributes
|
|
373
|
+
attributes.reject { |a| a.reference? || a.attachment? || a.attachments? || a.rich_text? || a.token? || a.password_digest? }
|
|
350
374
|
end
|
|
351
375
|
end
|
|
352
376
|
|
|
@@ -354,6 +378,14 @@ module Spree
|
|
|
354
378
|
|
|
355
379
|
INITIALIZER_PATH = 'config/initializers/spree.rb'.freeze
|
|
356
380
|
|
|
381
|
+
# A migration column type → the `request_params` type that documents it.
|
|
382
|
+
REQUEST_PARAM_TYPES = {
|
|
383
|
+
string: :string, text: :string, citext: :string,
|
|
384
|
+
integer: :integer, bigint: :integer, float: :number, decimal: :decimal,
|
|
385
|
+
boolean: :boolean, date: :date, datetime: :datetime, timestamp: :datetime,
|
|
386
|
+
json: :hash, jsonb: :hash
|
|
387
|
+
}.freeze
|
|
388
|
+
|
|
357
389
|
def permission_scope_block
|
|
358
390
|
<<~RUBY
|
|
359
391
|
|
|
@@ -9,6 +9,15 @@ module Spree
|
|
|
9
9
|
# existing resource name instead if this endpoint belongs under one.
|
|
10
10
|
scoped_resource :<%= plural_name %>
|
|
11
11
|
|
|
12
|
+
# What create and update accept: the controller permits from it, and
|
|
13
|
+
# the integration spec documents it (`request_body_for`), which is
|
|
14
|
+
# where the SDK request types come from.
|
|
15
|
+
request_params :<%= singular_name %> do |body|
|
|
16
|
+
<% request_param_attributes.each do |name, type, required| -%>
|
|
17
|
+
body.attribute :<%= name %>, :<%= type %><%= ', required: true, on: :create' if required %>
|
|
18
|
+
<% end -%>
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
protected
|
|
13
22
|
|
|
14
23
|
def model_class
|
|
@@ -18,10 +27,6 @@ module Spree
|
|
|
18
27
|
def serializer_class
|
|
19
28
|
Spree::Api::V3::Admin::<%= bare_class_name %>Serializer
|
|
20
29
|
end
|
|
21
|
-
|
|
22
|
-
def resource_permitted_attributes
|
|
23
|
-
[<%= permitted_attribute_names.map { |a| ":#{a}" }.join(', ') %>]
|
|
24
|
-
end
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'swagger_helper'
|
|
4
|
+
|
|
5
|
+
# Documents the <%= plural_name.humanize(capitalize: false) %> Admin API in openapi/admin.yaml (`spree task
|
|
6
|
+
# openapi:generate`). The SDK request types come from these operations, and
|
|
7
|
+
# each action other than create, read, update and delete becomes an agent tool.
|
|
8
|
+
RSpec.describe '<%= plural_name.humanize %> Admin API', type: :request, swagger_doc: 'openapi/admin.yaml' do
|
|
9
|
+
let!(:<%= singular_name %>) { create(:<%= singular_name %>) }
|
|
10
|
+
|
|
11
|
+
path '/api/v3/admin/<%= plural_name %>' do
|
|
12
|
+
get 'List <%= plural_name.humanize(capitalize: false) %>' do
|
|
13
|
+
tags '<%= plural_name.humanize %>'
|
|
14
|
+
admin_operation :read, :<%= plural_name %>, description: 'Lists the <%= plural_name.humanize(capitalize: false) %>.'
|
|
15
|
+
parameter name: :page, in: :query, type: :integer, required: false, description: 'Page number'
|
|
16
|
+
parameter name: :limit, in: :query, type: :integer, required: false, description: 'Number of records per page'
|
|
17
|
+
filter_parameters_for
|
|
18
|
+
|
|
19
|
+
response '200', '<%= plural_name.humanize(capitalize: false) %> found' do
|
|
20
|
+
run_test! do |response|
|
|
21
|
+
expect(JSON.parse(response.body)['data'].map { |record| record['id'] }).to include(<%= singular_name %>.prefixed_id)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
post 'Create a <%= singular_name.humanize(capitalize: false) %>' do
|
|
27
|
+
tags '<%= plural_name.humanize %>'
|
|
28
|
+
admin_operation :write, :<%= plural_name %>, description: 'Creates a <%= singular_name.humanize(capitalize: false) %>.'
|
|
29
|
+
request_body_for
|
|
30
|
+
|
|
31
|
+
response '201', '<%= singular_name.humanize(capitalize: false) %> created' do
|
|
32
|
+
let(:body) { attributes_for(:<%= singular_name %>).slice(<%= permitted_attribute_names.map { |a| ":#{a}" }.join(', ') %>)<%= decimal_attribute_names.map { |a| ".merge(#{a}: '1.00')" }.join %> }
|
|
33
|
+
|
|
34
|
+
run_test!
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
path '/api/v3/admin/<%= plural_name %>/{id}' do
|
|
40
|
+
parameter name: :id, in: :path, type: :string, required: true, description: '<%= singular_name.humanize %> ID'
|
|
41
|
+
|
|
42
|
+
let(:id) { <%= singular_name %>.prefixed_id }
|
|
43
|
+
|
|
44
|
+
get 'Get a <%= singular_name.humanize(capitalize: false) %>' do
|
|
45
|
+
tags '<%= plural_name.humanize %>'
|
|
46
|
+
admin_operation :read, :<%= plural_name %>, description: 'Returns a <%= singular_name.humanize(capitalize: false) %>.'
|
|
47
|
+
|
|
48
|
+
response '200', '<%= singular_name.humanize(capitalize: false) %> found' do
|
|
49
|
+
run_test!
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
patch 'Update a <%= singular_name.humanize(capitalize: false) %>' do
|
|
54
|
+
tags '<%= plural_name.humanize %>'
|
|
55
|
+
admin_operation :write, :<%= plural_name %>, description: 'Updates a <%= singular_name.humanize(capitalize: false) %>.'
|
|
56
|
+
request_body_for
|
|
57
|
+
|
|
58
|
+
response '200', '<%= singular_name.humanize(capitalize: false) %> updated' do
|
|
59
|
+
let(:body) { attributes_for(:<%= singular_name %>).slice(<%= permitted_attribute_names.map { |a| ":#{a}" }.join(', ') %>)<%= decimal_attribute_names.map { |a| ".merge(#{a}: '1.00')" }.join %> }
|
|
60
|
+
|
|
61
|
+
run_test!
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
delete 'Delete a <%= singular_name.humanize(capitalize: false) %>' do
|
|
66
|
+
tags '<%= plural_name.humanize %>'
|
|
67
|
+
admin_operation :write, :<%= plural_name %>, description: 'Deletes a <%= singular_name.humanize(capitalize: false) %>.'
|
|
68
|
+
|
|
69
|
+
response '204', '<%= singular_name.humanize(capitalize: false) %> deleted' do
|
|
70
|
+
run_test!
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -3,6 +3,17 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Store
|
|
5
5
|
class <%= plural_name.camelize %>Controller < ResourceController
|
|
6
|
+
<% if writable? -%>
|
|
7
|
+
# What create and update accept: the controller permits from it, and
|
|
8
|
+
# the integration spec documents it (`request_body_for`), which is
|
|
9
|
+
# where the SDK request types come from.
|
|
10
|
+
request_params :<%= singular_name %> do |body|
|
|
11
|
+
<% request_param_attributes.each do |name, type, required| -%>
|
|
12
|
+
body.attribute :<%= name %>, :<%= type %><%= ', required: true, on: :create' if required %>
|
|
13
|
+
<% end -%>
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
<% end -%>
|
|
6
17
|
protected
|
|
7
18
|
|
|
8
19
|
def model_class
|
|
@@ -17,12 +28,6 @@ module Spree
|
|
|
17
28
|
def serializer_class
|
|
18
29
|
Spree::Api::V3::<%= bare_class_name %>Serializer
|
|
19
30
|
end
|
|
20
|
-
<% end -%>
|
|
21
|
-
<% if writable? -%>
|
|
22
|
-
|
|
23
|
-
def resource_permitted_attributes
|
|
24
|
-
[<%= permitted_attribute_names.map { |a| ":#{a}" }.join(', ') %>]
|
|
25
|
-
end
|
|
26
31
|
<% end -%>
|
|
27
32
|
end
|
|
28
33
|
end
|