spree_api 5.4.3 → 5.5.0.rc2
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/Rakefile +36 -0
- data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -0
- data/app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb +52 -0
- data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +149 -0
- data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +54 -0
- data/app/controllers/concerns/spree/api/v3/bulk_operations.rb +103 -0
- data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +60 -0
- data/app/controllers/concerns/spree/api/v3/error_handler.rb +4 -0
- data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +84 -0
- data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +104 -0
- data/app/controllers/concerns/spree/api/v3/store/search_provider_support.rb +35 -1
- data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +109 -0
- data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +25 -0
- data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +84 -0
- data/app/controllers/spree/api/v3/admin/auth_controller.rb +134 -0
- data/app/controllers/spree/api/v3/admin/base_controller.rb +3 -17
- data/app/controllers/spree/api/v3/admin/categories_controller.rb +25 -0
- data/app/controllers/spree/api/v3/admin/channels_controller.rb +65 -0
- data/app/controllers/spree/api/v3/admin/countries_controller.rb +38 -0
- data/app/controllers/spree/api/v3/admin/coupon_codes_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +34 -0
- data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +129 -0
- data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +83 -0
- data/app/controllers/spree/api/v3/admin/customers/base_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +25 -0
- data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +92 -0
- data/app/controllers/spree/api/v3/admin/customers_controller.rb +119 -0
- data/app/controllers/spree/api/v3/admin/dashboard_controller.rb +44 -0
- data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +40 -0
- data/app/controllers/spree/api/v3/admin/exports_controller.rb +136 -0
- data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +138 -0
- data/app/controllers/spree/api/v3/admin/invitations_controller.rb +81 -0
- data/app/controllers/spree/api/v3/admin/markets_controller.rb +42 -0
- data/app/controllers/spree/api/v3/admin/me_controller.rb +69 -0
- data/app/controllers/spree/api/v3/admin/media_controller.rb +119 -0
- data/app/controllers/spree/api/v3/admin/option_types_controller.rb +34 -0
- data/app/controllers/spree/api/v3/admin/orders/adjustments_controller.rb +27 -0
- data/app/controllers/spree/api/v3/admin/orders/base_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +104 -0
- data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +79 -0
- data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +92 -0
- data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +90 -0
- data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +53 -0
- data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +59 -0
- data/app/controllers/spree/api/v3/admin/orders_controller.rb +190 -0
- data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +73 -0
- data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +179 -0
- data/app/controllers/spree/api/v3/admin/prices_controller.rb +157 -0
- data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +48 -0
- data/app/controllers/spree/api/v3/admin/products_controller.rb +237 -0
- data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +78 -0
- data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +56 -0
- data/app/controllers/spree/api/v3/admin/promotions_controller.rb +78 -0
- data/app/controllers/spree/api/v3/admin/resource_controller.rb +29 -11
- data/app/controllers/spree/api/v3/admin/roles_controller.rb +29 -0
- data/app/controllers/spree/api/v3/admin/stock_items_controller.rb +35 -0
- data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +36 -0
- data/app/controllers/spree/api/v3/admin/stock_reservations_controller.rb +29 -0
- data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +75 -0
- data/app/controllers/spree/api/v3/admin/store_controller.rb +53 -0
- data/app/controllers/spree/api/v3/admin/store_credit_categories_controller.rb +21 -0
- data/app/controllers/spree/api/v3/admin/tags_controller.rb +51 -0
- data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +21 -0
- data/app/controllers/spree/api/v3/admin/variants_controller.rb +33 -0
- data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +49 -0
- data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +75 -0
- data/app/controllers/spree/api/v3/resource_controller.rb +117 -8
- data/app/controllers/spree/api/v3/store/auth_controller.rb +8 -28
- data/app/controllers/spree/api/v3/store/base_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/carts_controller.rb +1 -0
- data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -0
- data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +77 -0
- data/app/controllers/spree/api/v3/store/products/filters_controller.rb +2 -2
- data/app/controllers/spree/api/v3/store/products_controller.rb +4 -3
- data/app/controllers/spree/api/v3/store/resource_controller.rb +10 -2
- data/app/jobs/spree/webhook_delivery_job.rb +5 -0
- data/app/models/spree/api_key_ability.rb +16 -0
- data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/adjustment_serializer.rb +3 -15
- data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +19 -3
- data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +42 -0
- data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
- data/app/serializers/spree/api/v3/admin/channel_serializer.rb +15 -0
- data/app/serializers/spree/api/v3/admin/country_serializer.rb +1 -1
- data/app/serializers/spree/api/v3/admin/coupon_code_serializer.rb +30 -0
- data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/custom_field_definition_serializer.rb +21 -0
- data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +8 -3
- data/app/serializers/spree/api/v3/admin/customer_group_serializer.rb +27 -0
- data/app/serializers/spree/api/v3/admin/customer_serializer.rb +58 -2
- data/app/serializers/spree/api/v3/admin/dashboard_analytics_serializer.rb +143 -0
- data/app/serializers/spree/api/v3/admin/export_serializer.rb +40 -0
- data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/{asset_serializer.rb → gift_card_batch_serializer.rb} +1 -1
- data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +39 -4
- data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +64 -0
- data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +4 -16
- data/app/serializers/spree/api/v3/admin/media_serializer.rb +24 -2
- data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/order_serializer.rb +21 -6
- data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -6
- data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +4 -1
- data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +51 -0
- data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +55 -0
- data/app/serializers/spree/api/v3/admin/price_serializer.rb +4 -0
- data/app/serializers/spree/api/v3/admin/product_publication_serializer.rb +11 -0
- data/app/serializers/spree/api/v3/admin/product_serializer.rb +34 -10
- data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +71 -0
- data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +85 -0
- data/app/serializers/spree/api/v3/admin/promotion_serializer.rb +41 -0
- data/app/serializers/spree/api/v3/admin/refund_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/role_serializer.rb +17 -0
- data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +16 -1
- data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/admin/stock_reservation_serializer.rb +46 -0
- data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +37 -0
- data/app/serializers/spree/api/v3/admin/store_credit_category_serializer.rb +19 -0
- data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +11 -5
- data/app/serializers/spree/api/v3/admin/store_serializer.rb +55 -0
- data/app/serializers/spree/api/v3/admin/tax_category_serializer.rb +4 -2
- data/app/serializers/spree/api/v3/admin/variant_serializer.rb +37 -6
- data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +45 -0
- data/app/serializers/spree/api/v3/admin/webhook_endpoint_serializer.rb +69 -0
- data/app/serializers/spree/api/v3/channel_serializer.rb +14 -0
- data/app/serializers/spree/api/v3/custom_field_serializer.rb +9 -10
- data/app/serializers/spree/api/v3/customer_serializer.rb +5 -0
- data/app/serializers/spree/api/v3/market_serializer.rb +2 -1
- data/app/serializers/spree/api/v3/media_serializer.rb +8 -6
- data/app/serializers/spree/api/v3/order_serializer.rb +6 -1
- data/app/serializers/spree/api/v3/payment_method_serializer.rb +11 -2
- data/app/serializers/spree/api/v3/product_publication_serializer.rb +22 -0
- data/app/serializers/spree/api/v3/product_serializer.rb +6 -1
- data/app/serializers/spree/api/v3/stock_reservation_serializer.rb +10 -0
- data/config/locales/en.yml +2 -0
- data/config/routes.rb +235 -1
- data/lib/spree/api/configuration.rb +2 -2
- data/lib/spree/api/dependencies.rb +25 -1
- data/lib/spree/api/openapi/path_sorter.rb +126 -0
- data/lib/spree/api/openapi/schema_helper.rb +185 -6
- data/lib/spree/api/testing_support/v3/base.rb +28 -0
- metadata +98 -8
- data/app/serializers/spree/api/v3/admin/shipping_category_serializer.rb +0 -14
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Read-only listing of coupon codes. Codes are generated server-side
|
|
6
|
+
# based on the parent promotion's `code_prefix` and `number_of_codes`,
|
|
7
|
+
# so this controller intentionally only exposes index/show.
|
|
8
|
+
class CouponCodesController < ResourceController
|
|
9
|
+
scoped_resource :promotions
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def model_class
|
|
14
|
+
Spree::CouponCode
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serializer_class
|
|
18
|
+
Spree.api.admin_coupon_code_serializer
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_parent
|
|
22
|
+
@parent = Spree::Promotion.accessible_by(current_ability, :show)
|
|
23
|
+
.find_by_prefix_id!(params[:promotion_id])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parent_association
|
|
27
|
+
:coupon_codes
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Schema-side metadata for custom fields. Definitions are per resource
|
|
6
|
+
# *type* (every Spree::Product shares the same definitions), so this is
|
|
7
|
+
# a flat top-level endpoint. Filter by `?resource_type=Spree::Product`
|
|
8
|
+
# (or any other registered custom-field-bearing resource) to scope the
|
|
9
|
+
# list to one parent type.
|
|
10
|
+
class CustomFieldDefinitionsController < ResourceController
|
|
11
|
+
scoped_resource :settings
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def model_class
|
|
16
|
+
Spree::CustomFieldDefinition
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def serializer_class
|
|
20
|
+
Spree.api.admin_custom_field_definition_serializer
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# `label`, `field_type`, `storefront_visible` are model-side aliases
|
|
24
|
+
# (alias_attribute / accessors) on Spree::CustomFieldDefinition. The
|
|
25
|
+
# API → DB column rename lands in 6.0 and this controller stays flat.
|
|
26
|
+
def permitted_params
|
|
27
|
+
params.permit(:namespace, :key, :label, :field_type,
|
|
28
|
+
:resource_type, :storefront_visible)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Custom field values for any parent that includes the custom-fields
|
|
6
|
+
# concern. Mounted via the `:custom_fieldable` route concern; the parent
|
|
7
|
+
# class is inferred from whichever `<segment>_id` route param matches a
|
|
8
|
+
# registered owner.
|
|
9
|
+
class CustomFieldsController < ResourceController
|
|
10
|
+
# POST /api/v3/admin/<parent>/<parent_id>/custom_fields
|
|
11
|
+
def create
|
|
12
|
+
@resource = @parent.metafields.new(permitted_params)
|
|
13
|
+
authorize_resource!(@resource, :create)
|
|
14
|
+
|
|
15
|
+
if @resource.save
|
|
16
|
+
render json: serialize_resource(@resource), status: :created
|
|
17
|
+
else
|
|
18
|
+
render_validation_error(@resource.errors)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# PATCH /api/v3/admin/<parent>/<parent_id>/custom_fields/:id
|
|
23
|
+
# Only `value` is mutable. Switching the linked definition is a
|
|
24
|
+
# delete-and-create — the model rejects it (definition + resource
|
|
25
|
+
# uniqueness) and the type wouldn't match.
|
|
26
|
+
def update
|
|
27
|
+
authorize_resource!(@resource)
|
|
28
|
+
|
|
29
|
+
if @resource.update(update_permitted_params)
|
|
30
|
+
render json: serialize_resource(@resource)
|
|
31
|
+
else
|
|
32
|
+
render_validation_error(@resource.errors)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def model_class
|
|
39
|
+
Spree::CustomField
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def serializer_class
|
|
43
|
+
Spree.api.admin_custom_field_serializer
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parent_association
|
|
47
|
+
:metafields
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_parent
|
|
51
|
+
# Routes always mount this controller under a recognized parent, so
|
|
52
|
+
# `parent_lookup` matches in normal flows. The explicit raise is a
|
|
53
|
+
# defensive guard against a future route nesting that doesn't.
|
|
54
|
+
raise ActiveRecord::RecordNotFound, 'Parent resource not found' unless parent_lookup
|
|
55
|
+
|
|
56
|
+
@parent = parent_relation.find_by_prefix_id!(parent_lookup.value)
|
|
57
|
+
authorize!(:show, @parent)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Resolves the parent within the current store so a token for one
|
|
61
|
+
# store can't read or write custom fields on another store's records.
|
|
62
|
+
# Users are intentionally global in Spree (`User.for_store` is a
|
|
63
|
+
# no-op), so the store boundary for customer parents is enforced by
|
|
64
|
+
# the `authorize!(:show, @parent)` ability check above.
|
|
65
|
+
def parent_relation
|
|
66
|
+
klass = parent_lookup.klass
|
|
67
|
+
klass.respond_to?(:for_store) ? klass.for_store(current_store) : klass
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Product-family parents have no scope pair of their own — their
|
|
71
|
+
# custom fields are catalog data, gated by `products` like the rest
|
|
72
|
+
# of the variant/option-type surface.
|
|
73
|
+
SCOPE_OVERRIDES = { 'option_type' => :products, 'variant' => :products }.freeze
|
|
74
|
+
|
|
75
|
+
# Per-parent scope check: a key holding `write_products` may write a
|
|
76
|
+
# product's custom fields, `write_orders` may write an order's, etc.
|
|
77
|
+
# Resolves the parent at request time rather than via the static
|
|
78
|
+
# `scoped_resource` declaration.
|
|
79
|
+
def scoped_resource_name
|
|
80
|
+
segment = parent_lookup&.segment
|
|
81
|
+
return unless segment
|
|
82
|
+
|
|
83
|
+
SCOPE_OVERRIDES[segment] || segment.pluralize.to_sym
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# `custom_field_definition_id` is an alias_attribute on Spree::CustomField;
|
|
87
|
+
# AR resolves the prefixed-ID and the alias to the canonical FK on assign.
|
|
88
|
+
def permitted_params
|
|
89
|
+
params.permit(:custom_field_definition_id, :value)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def update_permitted_params
|
|
93
|
+
params.permit(:value)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
ParentLookup = Struct.new(:klass, :value, :segment)
|
|
99
|
+
|
|
100
|
+
# Stores class names (not class objects) so the map survives dev-mode
|
|
101
|
+
# code reloads — `enabled_resources` is captured at boot and its
|
|
102
|
+
# class references go stale. Aliases `'customer'` because the route
|
|
103
|
+
# uses `customer_id` while user_class.model_name.element is `'user'`,
|
|
104
|
+
# and `'category'` because the routes expose taxons as categories
|
|
105
|
+
# (5.5 rename) while the model's element is still `'taxon'`.
|
|
106
|
+
def parent_route_map
|
|
107
|
+
@parent_route_map ||= Spree.metafields.enabled_resources.each_with_object({}) do |klass, m|
|
|
108
|
+
m[klass.model_name.element.to_s] = klass.name
|
|
109
|
+
end.merge('customer' => Spree.user_class.name, 'category' => 'Spree::Taxon')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns the first segment whose `<segment>_id` is present in params,
|
|
113
|
+
# paired with its class and the raw id value, or nil. Memoized — read
|
|
114
|
+
# by both `set_parent` and `scoped_resource_name`.
|
|
115
|
+
def parent_lookup
|
|
116
|
+
return @parent_lookup if defined?(@parent_lookup)
|
|
117
|
+
|
|
118
|
+
match = parent_route_map.find { |segment, _| params[:"#{segment}_id"].present? }
|
|
119
|
+
@parent_lookup =
|
|
120
|
+
if match
|
|
121
|
+
segment, klass_name = match
|
|
122
|
+
ParentLookup.new(klass_name.constantize, params[:"#{segment}_id"], segment)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin CRUD for `Spree::CustomerGroup`. Scoped to the current
|
|
6
|
+
# store so groups from sibling stores don't leak into pickers.
|
|
7
|
+
class CustomerGroupsController < ResourceController
|
|
8
|
+
scoped_resource :customers
|
|
9
|
+
|
|
10
|
+
protected
|
|
11
|
+
|
|
12
|
+
def model_class
|
|
13
|
+
Spree::CustomerGroup
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serializer_class
|
|
17
|
+
Spree.api.admin_customer_group_serializer
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def scope
|
|
21
|
+
super.for_store(current_store)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def permitted_params
|
|
25
|
+
params.permit(:name, :description, customer_ids: [])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class AddressesController < BaseController
|
|
7
|
+
|
|
8
|
+
# POST /api/v3/admin/customers/:customer_id/addresses
|
|
9
|
+
def create
|
|
10
|
+
@resource = @parent.addresses.new(address_attrs)
|
|
11
|
+
authorize_resource!(@resource, :create)
|
|
12
|
+
|
|
13
|
+
ApplicationRecord.transaction do
|
|
14
|
+
if @resource.save
|
|
15
|
+
apply_default_flags(@resource)
|
|
16
|
+
render json: serialize_resource(@resource.reload), status: :created
|
|
17
|
+
else
|
|
18
|
+
render_validation_error(@resource.errors)
|
|
19
|
+
raise ActiveRecord::Rollback
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# PATCH /api/v3/admin/customers/:customer_id/addresses/:id
|
|
25
|
+
def update
|
|
26
|
+
authorize_resource!(@resource)
|
|
27
|
+
|
|
28
|
+
ApplicationRecord.transaction do
|
|
29
|
+
if @resource.update(address_attrs)
|
|
30
|
+
apply_default_flags(@resource)
|
|
31
|
+
render json: serialize_resource(@resource.reload)
|
|
32
|
+
else
|
|
33
|
+
render_validation_error(@resource.errors)
|
|
34
|
+
raise ActiveRecord::Rollback
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# DELETE /api/v3/admin/customers/:customer_id/addresses/:id
|
|
40
|
+
def destroy
|
|
41
|
+
authorize_resource!(@resource)
|
|
42
|
+
@resource.destroy
|
|
43
|
+
head :no_content
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
protected
|
|
47
|
+
|
|
48
|
+
def parent_association
|
|
49
|
+
:addresses
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def model_class
|
|
53
|
+
Spree::Address
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def serializer_class
|
|
57
|
+
Spree.api.admin_address_serializer
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def address_attrs
|
|
63
|
+
params.permit(
|
|
64
|
+
:firstname, :lastname, :first_name, :last_name,
|
|
65
|
+
:address1, :address2, :city,
|
|
66
|
+
:country_iso, :state_abbr, :country_id, :state_id,
|
|
67
|
+
:zipcode, :postal_code, :phone, :alternative_phone,
|
|
68
|
+
:state_name, :company, :label, :quick_checkout
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def apply_default_flags(address)
|
|
73
|
+
updates = {}
|
|
74
|
+
updates[:bill_address_id] = address.id if ActiveModel::Type::Boolean.new.cast(params[:is_default_billing])
|
|
75
|
+
updates[:ship_address_id] = address.id if ActiveModel::Type::Boolean.new.cast(params[:is_default_shipping])
|
|
76
|
+
@parent.update!(updates) if updates.any?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
# Shared base for resources nested under a customer
|
|
7
|
+
# (`/admin/customers/:customer_id/...`). Resolves the parent customer
|
|
8
|
+
# and authorizes it per action (`:show` for reads, `:update` for
|
|
9
|
+
# writes) so a role that can only view a customer can't mutate its
|
|
10
|
+
# nested collections. Mirrors `Orders::BaseController`.
|
|
11
|
+
class BaseController < ResourceController
|
|
12
|
+
scoped_resource :customers
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
# Resolve the customer through the ability-scoped relation, using
|
|
17
|
+
# the action-appropriate ability on the parent (`:show` for reads,
|
|
18
|
+
# `:update` for writes — see `parent_ability_action`). A customer
|
|
19
|
+
# the caller can't access for the requested action is filtered out
|
|
20
|
+
# and 404s, rather than leaking its existence as a 403. Users are
|
|
21
|
+
# global in Spree (`User.for_store` is a no-op), so the ability is
|
|
22
|
+
# the only boundary here.
|
|
23
|
+
def set_parent
|
|
24
|
+
@parent = Spree.user_class.
|
|
25
|
+
accessible_by(current_ability, parent_ability_action).
|
|
26
|
+
find_by_prefix_id!(params[:customer_id])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class CreditCardsController < BaseController
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
def parent_association
|
|
10
|
+
:credit_cards
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def model_class
|
|
14
|
+
Spree::CreditCard
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serializer_class
|
|
18
|
+
Spree.api.admin_credit_card_serializer
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class StoreCreditsController < BaseController
|
|
7
|
+
# Store credits gate on their own scope rather than the parent's
|
|
8
|
+
# `:customers`, so a store-credit integration key doesn't need
|
|
9
|
+
# broad customer access.
|
|
10
|
+
scoped_resource :store_credits
|
|
11
|
+
|
|
12
|
+
# POST /api/v3/admin/customers/:customer_id/store_credits
|
|
13
|
+
def create
|
|
14
|
+
@resource = @parent.store_credits.new(create_attrs)
|
|
15
|
+
@resource.created_by = try_spree_current_user
|
|
16
|
+
@resource.store ||= current_store
|
|
17
|
+
@resource.category ||= Spree::StoreCreditCategory.first
|
|
18
|
+
authorize_resource!(@resource, :create)
|
|
19
|
+
|
|
20
|
+
if @resource.save
|
|
21
|
+
render json: serialize_resource(@resource), status: :created
|
|
22
|
+
else
|
|
23
|
+
render_validation_error(@resource.errors)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PATCH /api/v3/admin/customers/:customer_id/store_credits/:id
|
|
28
|
+
def update
|
|
29
|
+
authorize_resource!(@resource)
|
|
30
|
+
|
|
31
|
+
if @resource.amount_used.positive? && update_attrs.key?(:amount)
|
|
32
|
+
render_error(
|
|
33
|
+
code: 'store_credit_in_use',
|
|
34
|
+
message: 'Cannot change amount on a store credit that has already been used',
|
|
35
|
+
status: :unprocessable_content
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if @resource.update(update_attrs)
|
|
41
|
+
render json: serialize_resource(@resource.reload)
|
|
42
|
+
else
|
|
43
|
+
render_validation_error(@resource.errors)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# DELETE /api/v3/admin/customers/:customer_id/store_credits/:id
|
|
48
|
+
def destroy
|
|
49
|
+
authorize_resource!(@resource)
|
|
50
|
+
|
|
51
|
+
if @resource.amount_used.positive?
|
|
52
|
+
render_error(
|
|
53
|
+
code: 'store_credit_in_use',
|
|
54
|
+
message: 'Cannot delete a store credit that has already been used',
|
|
55
|
+
status: :unprocessable_content
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@resource.destroy
|
|
61
|
+
head :no_content
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
protected
|
|
65
|
+
|
|
66
|
+
def parent_association
|
|
67
|
+
:store_credits
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def model_class
|
|
71
|
+
Spree::StoreCredit
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def serializer_class
|
|
75
|
+
Spree.api.admin_store_credit_serializer
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def create_attrs
|
|
81
|
+
params.permit(:amount, :currency, :category_id, :memo).to_h
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_attrs
|
|
85
|
+
params.permit(:memo, :category_id, :amount).to_h
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class CustomersController < ResourceController
|
|
6
|
+
include Spree::Api::V3::BulkOperations
|
|
7
|
+
|
|
8
|
+
scoped_resource :customers
|
|
9
|
+
|
|
10
|
+
before_action :require_ids!, only: [:bulk_add_to_groups, :bulk_remove_from_groups]
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
@resource = Spree.user_class.new(permitted_params)
|
|
14
|
+
# Admin-created customers don't pick a password upfront — they
|
|
15
|
+
# claim the account via password reset later.
|
|
16
|
+
# `Spree::UserMethods` exposes `skip_password_validation` so
|
|
17
|
+
# Devise's `:validatable` lets a nil credential through on this
|
|
18
|
+
# code path. Storefront registration never sets the flag, so
|
|
19
|
+
# customer self-signup still requires a password.
|
|
20
|
+
@resource.skip_password_validation = true if @resource.password.blank?
|
|
21
|
+
authorize!(:create, @resource)
|
|
22
|
+
|
|
23
|
+
if @resource.save
|
|
24
|
+
render json: serialize_resource(@resource), status: :created
|
|
25
|
+
else
|
|
26
|
+
render_validation_error(@resource.errors)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update
|
|
31
|
+
authorize_resource!(@resource)
|
|
32
|
+
|
|
33
|
+
if @resource.update(permitted_params)
|
|
34
|
+
render json: serialize_resource(@resource.reload)
|
|
35
|
+
else
|
|
36
|
+
render_validation_error(@resource.errors)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def destroy
|
|
41
|
+
authorize_resource!(@resource)
|
|
42
|
+
@resource.destroy
|
|
43
|
+
head :no_content
|
|
44
|
+
rescue Spree::Core::DestroyWithOrdersError => e
|
|
45
|
+
render_error(
|
|
46
|
+
code: 'customer_has_orders',
|
|
47
|
+
message: e.message.presence || Spree.t(:error_user_destroy_with_orders),
|
|
48
|
+
status: :unprocessable_content
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Bulk add the given customers to the given groups. Idempotent —
|
|
53
|
+
# customers already in a group are skipped at the model layer.
|
|
54
|
+
def bulk_add_to_groups
|
|
55
|
+
apply_groups(:add_customers)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Bulk remove the given customers from the given groups.
|
|
59
|
+
def bulk_remove_from_groups
|
|
60
|
+
apply_groups(:remove_customers)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
def model_class
|
|
66
|
+
Spree.user_class
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def serializer_class
|
|
70
|
+
Spree.api.admin_customer_serializer
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def scope
|
|
74
|
+
super.with_order_aggregates
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def collection_includes
|
|
78
|
+
[:rich_text_internal_note, taggings: :tag]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Mirrors the products controller's resource-named key so SPA toasts
|
|
84
|
+
# can substitute `{customer_count}` instead of the generic
|
|
85
|
+
# `{record_count}` shipped by `Spree::Api::V3::BulkOperations`.
|
|
86
|
+
def bulk_record_count_key
|
|
87
|
+
:customer_count
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def permitted_params
|
|
91
|
+
params.permit(
|
|
92
|
+
:email, :first_name, :last_name, :phone,
|
|
93
|
+
:password, :password_confirmation, :selected_locale,
|
|
94
|
+
:avatar, :accepts_email_marketing, :internal_note,
|
|
95
|
+
metadata: {}, tags: []
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Authorises bulk group mutation, decodes prefixed IDs, then dispatches
|
|
100
|
+
# to `add_customers` / `remove_customers` per group. Returns the
|
|
101
|
+
# counts of records actually affected so the UI can show a toast.
|
|
102
|
+
def apply_groups(method)
|
|
103
|
+
authorize! :update, model_class
|
|
104
|
+
|
|
105
|
+
user_ids = decode_ids(params[:ids])
|
|
106
|
+
group_ids = decode_ids(params[:customer_group_ids])
|
|
107
|
+
|
|
108
|
+
scoped_user_ids = scope.where(id: user_ids).pluck(:id)
|
|
109
|
+
scoped_groups = Spree::CustomerGroup.for_store(current_store).where(id: group_ids)
|
|
110
|
+
|
|
111
|
+
scoped_groups.find_each { |group| group.public_send(method, scoped_user_ids) }
|
|
112
|
+
|
|
113
|
+
render json: { customer_count: scoped_user_ids.size, customer_group_count: scoped_groups.size }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class DashboardController < Admin::BaseController
|
|
6
|
+
scoped_resource :dashboard
|
|
7
|
+
|
|
8
|
+
# GET /api/v3/admin/dashboard/analytics
|
|
9
|
+
def analytics
|
|
10
|
+
date_from = (params[:date_from] || 30.days.ago).to_time.beginning_of_day
|
|
11
|
+
date_to = (params[:date_to] || Time.current).to_time.end_of_day
|
|
12
|
+
currency = params[:currency] || current_store.default_currency
|
|
13
|
+
|
|
14
|
+
serializer = DashboardAnalyticsSerializer.new(
|
|
15
|
+
store: current_store,
|
|
16
|
+
currency: currency,
|
|
17
|
+
time_range: date_from..date_to,
|
|
18
|
+
params: serializer_params
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
render json: serializer.to_h
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def action_kind
|
|
27
|
+
'read'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def serializer_params
|
|
31
|
+
{
|
|
32
|
+
store: current_store,
|
|
33
|
+
locale: current_locale,
|
|
34
|
+
currency: current_currency,
|
|
35
|
+
user: current_user,
|
|
36
|
+
includes: [],
|
|
37
|
+
expand: []
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class DirectUploadsController < Admin::BaseController
|
|
6
|
+
# Direct uploads is a write-adjacent presigning helper: callers exchange
|
|
7
|
+
# blob metadata for an upload URL, then reference the resulting
|
|
8
|
+
# signed_id when creating/updating a resource (product media, customer
|
|
9
|
+
# avatar, etc). The narrowest scope it can map to is `write_products`
|
|
10
|
+
# since that covers the dominant upload flow (product/variant media).
|
|
11
|
+
# Other admin-write flows that take signed_ids (e.g. customer avatar)
|
|
12
|
+
# already require the relevant `write_<resource>` scope on the
|
|
13
|
+
# subsequent PATCH, so this gate is the floor, not the only check.
|
|
14
|
+
scoped_resource :products
|
|
15
|
+
|
|
16
|
+
skip_before_action :authenticate_user
|
|
17
|
+
|
|
18
|
+
# POST /api/v3/admin/direct_uploads
|
|
19
|
+
def create
|
|
20
|
+
blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_params)
|
|
21
|
+
|
|
22
|
+
render json: {
|
|
23
|
+
direct_upload: {
|
|
24
|
+
url: blob.service_url_for_direct_upload,
|
|
25
|
+
headers: blob.service_headers_for_direct_upload
|
|
26
|
+
},
|
|
27
|
+
signed_id: blob.signed_id
|
|
28
|
+
}, status: :created
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def blob_params
|
|
34
|
+
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type).to_h.symbolize_keys
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|