spree_api 5.4.3 → 5.5.0.rc1
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 +19 -0
- data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -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 +88 -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 +97 -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 +55 -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 +108 -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 +88 -0
- data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +31 -0
- data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +93 -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 +89 -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 +70 -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 +26 -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 +156 -0
- data/app/controllers/spree/api/v3/admin/prices_controller.rb +129 -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 +90 -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 +3 -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
- metadata +96 -8
- data/app/serializers/spree/api/v3/admin/shipping_category_serializer.rb +0 -14
|
@@ -0,0 +1,108 @@
|
|
|
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_lookup.klass.find_by_prefix_id!(parent_lookup.value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Per-parent scope check: a key holding `write_products` may write a
|
|
60
|
+
# product's custom fields, `write_orders` may write an order's, etc.
|
|
61
|
+
# Resolves the parent at request time rather than via the static
|
|
62
|
+
# `scoped_resource` declaration.
|
|
63
|
+
def scoped_resource_name
|
|
64
|
+
parent_lookup&.segment&.pluralize&.to_sym
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# `custom_field_definition_id` is an alias_attribute on Spree::CustomField;
|
|
68
|
+
# AR resolves the prefixed-ID and the alias to the canonical FK on assign.
|
|
69
|
+
def permitted_params
|
|
70
|
+
params.permit(:custom_field_definition_id, :value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def update_permitted_params
|
|
74
|
+
params.permit(:value)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
ParentLookup = Struct.new(:klass, :value, :segment)
|
|
80
|
+
|
|
81
|
+
# Stores class names (not class objects) so the map survives dev-mode
|
|
82
|
+
# code reloads — `enabled_resources` is captured at boot and its
|
|
83
|
+
# class references go stale. Aliases `'customer'` because the route
|
|
84
|
+
# uses `customer_id` while user_class.model_name.element is `'user'`.
|
|
85
|
+
def parent_route_map
|
|
86
|
+
@parent_route_map ||= Spree.metafields.enabled_resources.each_with_object({}) do |klass, m|
|
|
87
|
+
m[klass.model_name.element.to_s] = klass.name
|
|
88
|
+
end.merge('customer' => Spree.user_class.name)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the first segment whose `<segment>_id` is present in params,
|
|
92
|
+
# paired with its class and the raw id value, or nil. Memoized — read
|
|
93
|
+
# by both `set_parent` and `scoped_resource_name`.
|
|
94
|
+
def parent_lookup
|
|
95
|
+
return @parent_lookup if defined?(@parent_lookup)
|
|
96
|
+
|
|
97
|
+
match = parent_route_map.find { |segment, _| params[:"#{segment}_id"].present? }
|
|
98
|
+
@parent_lookup =
|
|
99
|
+
if match
|
|
100
|
+
segment, klass_name = match
|
|
101
|
+
ParentLookup.new(klass_name.constantize, params[:"#{segment}_id"], segment)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
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,88 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class AddressesController < ResourceController
|
|
7
|
+
scoped_resource :customers
|
|
8
|
+
|
|
9
|
+
# POST /api/v3/admin/customers/:customer_id/addresses
|
|
10
|
+
def create
|
|
11
|
+
@resource = @parent.addresses.new(address_attrs)
|
|
12
|
+
authorize_resource!(@resource, :create)
|
|
13
|
+
|
|
14
|
+
ApplicationRecord.transaction do
|
|
15
|
+
if @resource.save
|
|
16
|
+
apply_default_flags(@resource)
|
|
17
|
+
render json: serialize_resource(@resource.reload), status: :created
|
|
18
|
+
else
|
|
19
|
+
render_validation_error(@resource.errors)
|
|
20
|
+
raise ActiveRecord::Rollback
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# PATCH /api/v3/admin/customers/:customer_id/addresses/:id
|
|
26
|
+
def update
|
|
27
|
+
authorize_resource!(@resource)
|
|
28
|
+
|
|
29
|
+
ApplicationRecord.transaction do
|
|
30
|
+
if @resource.update(address_attrs)
|
|
31
|
+
apply_default_flags(@resource)
|
|
32
|
+
render json: serialize_resource(@resource.reload)
|
|
33
|
+
else
|
|
34
|
+
render_validation_error(@resource.errors)
|
|
35
|
+
raise ActiveRecord::Rollback
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# DELETE /api/v3/admin/customers/:customer_id/addresses/:id
|
|
41
|
+
def destroy
|
|
42
|
+
authorize_resource!(@resource)
|
|
43
|
+
@resource.destroy
|
|
44
|
+
head :no_content
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def set_parent
|
|
50
|
+
@parent = Spree.user_class.find_by_prefix_id!(params[:customer_id])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parent_association
|
|
54
|
+
:addresses
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def model_class
|
|
58
|
+
Spree::Address
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def serializer_class
|
|
62
|
+
Spree.api.admin_address_serializer
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def address_attrs
|
|
68
|
+
params.permit(
|
|
69
|
+
:firstname, :lastname, :first_name, :last_name,
|
|
70
|
+
:address1, :address2, :city,
|
|
71
|
+
:country_iso, :state_abbr, :country_id, :state_id,
|
|
72
|
+
:zipcode, :postal_code, :phone, :alternative_phone,
|
|
73
|
+
:state_name, :company, :label, :quick_checkout
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def apply_default_flags(address)
|
|
78
|
+
updates = {}
|
|
79
|
+
updates[:bill_address_id] = address.id if ActiveModel::Type::Boolean.new.cast(params[:is_default_billing])
|
|
80
|
+
updates[:ship_address_id] = address.id if ActiveModel::Type::Boolean.new.cast(params[:is_default_shipping])
|
|
81
|
+
@parent.update!(updates) if updates.any?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class CreditCardsController < ResourceController
|
|
7
|
+
scoped_resource :customers
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def set_parent
|
|
12
|
+
@parent = Spree.user_class.find_by_prefix_id!(params[:customer_id])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parent_association
|
|
16
|
+
:credit_cards
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def model_class
|
|
20
|
+
Spree::CreditCard
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serializer_class
|
|
24
|
+
Spree.api.admin_credit_card_serializer
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Customers
|
|
6
|
+
class StoreCreditsController < ResourceController
|
|
7
|
+
scoped_resource :store_credits
|
|
8
|
+
|
|
9
|
+
# POST /api/v3/admin/customers/:customer_id/store_credits
|
|
10
|
+
def create
|
|
11
|
+
@resource = @parent.store_credits.new(create_attrs)
|
|
12
|
+
@resource.created_by = try_spree_current_user
|
|
13
|
+
@resource.store ||= current_store
|
|
14
|
+
@resource.category ||= Spree::StoreCreditCategory.first
|
|
15
|
+
authorize_resource!(@resource, :create)
|
|
16
|
+
|
|
17
|
+
if @resource.save
|
|
18
|
+
render json: serialize_resource(@resource), status: :created
|
|
19
|
+
else
|
|
20
|
+
render_validation_error(@resource.errors)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# PATCH /api/v3/admin/customers/:customer_id/store_credits/:id
|
|
25
|
+
def update
|
|
26
|
+
authorize_resource!(@resource)
|
|
27
|
+
|
|
28
|
+
if @resource.amount_used.positive? && update_attrs.key?(:amount)
|
|
29
|
+
render_error(
|
|
30
|
+
code: 'store_credit_in_use',
|
|
31
|
+
message: 'Cannot change amount on a store credit that has already been used',
|
|
32
|
+
status: :unprocessable_content
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if @resource.update(update_attrs)
|
|
38
|
+
render json: serialize_resource(@resource.reload)
|
|
39
|
+
else
|
|
40
|
+
render_validation_error(@resource.errors)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# DELETE /api/v3/admin/customers/:customer_id/store_credits/:id
|
|
45
|
+
def destroy
|
|
46
|
+
authorize_resource!(@resource)
|
|
47
|
+
|
|
48
|
+
if @resource.amount_used.positive?
|
|
49
|
+
render_error(
|
|
50
|
+
code: 'store_credit_in_use',
|
|
51
|
+
message: 'Cannot delete a store credit that has already been used',
|
|
52
|
+
status: :unprocessable_content
|
|
53
|
+
)
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@resource.destroy
|
|
58
|
+
head :no_content
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
def set_parent
|
|
64
|
+
@parent = Spree.user_class.find_by_prefix_id!(params[:customer_id])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parent_association
|
|
68
|
+
:store_credits
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def model_class
|
|
72
|
+
Spree::StoreCredit
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def serializer_class
|
|
76
|
+
Spree.api.admin_store_credit_serializer
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def create_attrs
|
|
82
|
+
params.permit(:amount, :currency, :category_id, :memo).to_h
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def update_attrs
|
|
86
|
+
params.permit(:memo, :category_id, :amount).to_h
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
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
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# See `docs/plans/5.5-admin-spa-csv-export.md`.
|
|
6
|
+
class ExportsController < ResourceController
|
|
7
|
+
include ActiveStorage::SetCurrent
|
|
8
|
+
|
|
9
|
+
scoped_resource :exports
|
|
10
|
+
|
|
11
|
+
# We stream the CSV inline rather than redirecting to ActiveStorage's
|
|
12
|
+
# signed-URL endpoint because the SPA's Vite proxy only forwards
|
|
13
|
+
# `/api/*`. A cross-origin redirect to `/rails/active_storage/...`
|
|
14
|
+
# strips the Authorization header and the download fails silently.
|
|
15
|
+
def download
|
|
16
|
+
@resource = find_resource
|
|
17
|
+
authorize_resource!(@resource, :show)
|
|
18
|
+
|
|
19
|
+
unless @resource.done?
|
|
20
|
+
return render_error(
|
|
21
|
+
code: Spree::Api::V3::ErrorHandler::ERROR_CODES[:export_not_ready],
|
|
22
|
+
message: 'Export is not ready yet',
|
|
23
|
+
status: :unprocessable_content
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attachment = @resource.attachment
|
|
28
|
+
send_data(
|
|
29
|
+
attachment.download,
|
|
30
|
+
filename: attachment.filename.to_s,
|
|
31
|
+
type: attachment.content_type || 'text/csv',
|
|
32
|
+
disposition: 'attachment'
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
def model_class
|
|
39
|
+
Spree::Export
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def serializer_class
|
|
43
|
+
Spree.api.admin_export_serializer
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scope_includes
|
|
47
|
+
[:user, { attachment_attachment: :blob }]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_resource
|
|
51
|
+
klass = resolve_export_type(permitted_params[:type]) || Spree::Export
|
|
52
|
+
attrs = permitted_params.except(:type).merge(
|
|
53
|
+
store: current_store,
|
|
54
|
+
user: try_spree_current_user
|
|
55
|
+
)
|
|
56
|
+
klass.new(attrs)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# `search_params` carries an arbitrary Ransack hash with nested
|
|
60
|
+
# groupings (`{ g: [{ name_cont: 'foo' }] }`). Rails' `permit(k: {})`
|
|
61
|
+
# rejects nested hashes, so we extract via `to_unsafe_h`. `:format`
|
|
62
|
+
# is intentionally dropped — only CSV is supported and Rails' request
|
|
63
|
+
# format would otherwise overwrite the model's enum.
|
|
64
|
+
def permitted_params
|
|
65
|
+
attrs = params.permit(:type, :record_selection)
|
|
66
|
+
raw = params[:search_params]
|
|
67
|
+
attrs[:search_params] = raw.respond_to?(:to_unsafe_h) ? raw.to_unsafe_h : raw if raw.present?
|
|
68
|
+
attrs
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the registered Export subclass matching `name`, or nil.
|
|
72
|
+
#
|
|
73
|
+
# The constantize target comes from `available_types` (a trusted
|
|
74
|
+
# in-process registry), not from the request — `name` is only used
|
|
75
|
+
# to *select* an entry in the allowlist. This keeps the data flow
|
|
76
|
+
# from user input → trusted-string → `constantize` legible to
|
|
77
|
+
# static analyzers (CodeQL otherwise flags the inverse pattern of
|
|
78
|
+
# gating user input with `include?` before calling `constantize`).
|
|
79
|
+
def resolve_export_type(name)
|
|
80
|
+
return nil if name.blank?
|
|
81
|
+
|
|
82
|
+
target = Spree::Export.available_types.map(&:to_s).find { |t| t == name.to_s }
|
|
83
|
+
target&.constantize
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin bulk-issue endpoint for `Spree::GiftCardBatch`. Creating a
|
|
6
|
+
# batch synchronously generates the `codes_count` gift cards inline
|
|
7
|
+
# (or kicks off a background job when the count exceeds
|
|
8
|
+
# `Spree.config.gift_card_batch_web_limit`, default 500). Read-only
|
|
9
|
+
# access lives behind `list`/`show` so the SPA can surface batch
|
|
10
|
+
# context on the gift cards index (filter chip, batch chip on rows).
|
|
11
|
+
class GiftCardBatchesController < ResourceController
|
|
12
|
+
scoped_resource :gift_cards
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def model_class
|
|
17
|
+
Spree::GiftCardBatch
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serializer_class
|
|
21
|
+
Spree.api.admin_gift_card_batch_serializer
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def permitted_params
|
|
25
|
+
params.permit(:prefix, :codes_count, :amount, :expires_at, :currency)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|