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
|
@@ -5,6 +5,16 @@ module Spree
|
|
|
5
5
|
module SearchProviderSupport
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
+
# Matches `*_id_in`/`id_eq`/etc. and the bare `id_in`/`id_eq` on
|
|
9
|
+
# the resource's primary key. Mirrors the regex on
|
|
10
|
+
# `ResourceController` — duplicated here because `FiltersController`
|
|
11
|
+
# extends `Store::BaseController`, not `ResourceController`, and
|
|
12
|
+
# would otherwise NoMethodError on `decode_prefixed_id_predicates`.
|
|
13
|
+
# Requires a Ransack-predicate suffix so we don't match scope
|
|
14
|
+
# names like `with_option_value_ids` (which handle their own
|
|
15
|
+
# prefix decoding).
|
|
16
|
+
RANSACK_ID_PREDICATE_RE = /(?:\A|_)id(?:s)?_(?:eq|not_eq|in|not_in|lt|lteq|gt|gteq)\z/.freeze
|
|
17
|
+
|
|
8
18
|
private
|
|
9
19
|
|
|
10
20
|
def search_query
|
|
@@ -14,12 +24,36 @@ module Spree
|
|
|
14
24
|
def search_filters
|
|
15
25
|
q = params[:q]&.to_unsafe_h || params[:q] || {}
|
|
16
26
|
q = q.to_h if q.respond_to?(:to_h) && !q.is_a?(Hash)
|
|
17
|
-
|
|
27
|
+
# Decode Stripe-style prefixed IDs in `*_id_in`/`id_eq`/etc. so
|
|
28
|
+
# SPA + storefront filters can pass `prod_…` keys; the search
|
|
29
|
+
# provider hands the filter hash straight to Ransack on the
|
|
30
|
+
# underlying scope, which expects raw integer IDs.
|
|
31
|
+
decode_prefixed_id_predicates(q.except('search')).presence
|
|
18
32
|
end
|
|
19
33
|
|
|
20
34
|
def search_provider
|
|
21
35
|
@search_provider ||= Spree.search_provider.constantize.new(current_store)
|
|
22
36
|
end
|
|
37
|
+
|
|
38
|
+
def decode_prefixed_id_predicates(hash)
|
|
39
|
+
return hash unless hash.is_a?(Hash)
|
|
40
|
+
|
|
41
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
42
|
+
result[key] = if ransack_id_predicate?(key)
|
|
43
|
+
Array(value).map { |v| Spree::PrefixedId.prefixed_id?(v) ? Spree::PrefixedId.decode_prefixed_id(v) || v : v }.then { |arr|
|
|
44
|
+
value.is_a?(Array) ? arr : arr.first
|
|
45
|
+
}
|
|
46
|
+
elsif value.is_a?(Hash)
|
|
47
|
+
decode_prefixed_id_predicates(value)
|
|
48
|
+
else
|
|
49
|
+
value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ransack_id_predicate?(key)
|
|
55
|
+
RANSACK_ID_PREDICATE_RE.match?(key.to_s)
|
|
56
|
+
end
|
|
23
57
|
end
|
|
24
58
|
end
|
|
25
59
|
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Manages staff for the current store. "Staff" = admin users with at
|
|
6
|
+
# least one `Spree::RoleUser` whose `resource` is the current store.
|
|
7
|
+
# The legacy controller hard-deletes the global account on destroy;
|
|
8
|
+
# this v3 endpoint instead removes the per-store `RoleUser` rows so
|
|
9
|
+
# the user keeps their account (and access to other stores).
|
|
10
|
+
class AdminUsersController < ResourceController
|
|
11
|
+
scoped_resource :settings
|
|
12
|
+
|
|
13
|
+
# POST is not exposed — staff are created via invitations.
|
|
14
|
+
def create
|
|
15
|
+
head :method_not_allowed
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# DELETE /api/v3/admin/admin_users/:id
|
|
19
|
+
# Removes role assignments for the current store rather than deleting
|
|
20
|
+
# the account globally. The user keeps access to any other stores.
|
|
21
|
+
def destroy
|
|
22
|
+
authorize!(:destroy, @resource)
|
|
23
|
+
@resource.role_users.where(resource: current_store).destroy_all
|
|
24
|
+
head :no_content
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PATCH allows updating identity fields and replacing the user's
|
|
28
|
+
# roles for this store. `role_ids` accepts prefixed IDs and is
|
|
29
|
+
# applied via `add_role`/`remove_role` so the change is scoped to
|
|
30
|
+
# `current_store` and never touches other-store assignments.
|
|
31
|
+
def update
|
|
32
|
+
authorize!(:update, @resource)
|
|
33
|
+
|
|
34
|
+
attrs = identity_params
|
|
35
|
+
if @resource.update(attrs)
|
|
36
|
+
apply_role_ids(role_ids_param) if params.key?(:role_ids)
|
|
37
|
+
render json: serialize_resource(@resource)
|
|
38
|
+
else
|
|
39
|
+
render_validation_error(@resource.errors)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def model_class
|
|
46
|
+
Spree.admin_user_class
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def serializer_class
|
|
50
|
+
Spree.api.admin_admin_user_serializer
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def collection_includes
|
|
54
|
+
[{ role_users: :role }]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Restrict to users with a role assignment on the current store.
|
|
58
|
+
# `accessible_by` enforces CanCanCan on top.
|
|
59
|
+
def scope
|
|
60
|
+
model_class.
|
|
61
|
+
joins(:role_users).
|
|
62
|
+
where(spree_role_users: { resource: current_store }).
|
|
63
|
+
distinct.
|
|
64
|
+
accessible_by(current_ability, :show)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def identity_params
|
|
70
|
+
params.permit(:first_name, :last_name)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def role_ids_param
|
|
74
|
+
ids = Array(params[:role_ids])
|
|
75
|
+
ids.map { |id| Spree::PrefixedId.prefixed_id?(id) ? Spree::PrefixedId.decode_prefixed_id(id) : id }.compact
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Reconcile the user's roles on this store to match `desired_role_ids`.
|
|
79
|
+
# Adds missing assignments and removes extras — no-op for unchanged.
|
|
80
|
+
def apply_role_ids(desired_role_ids)
|
|
81
|
+
current = @resource.role_users.where(resource: current_store).pluck(:role_id).map(&:to_s)
|
|
82
|
+
target = desired_role_ids.map(&:to_s)
|
|
83
|
+
|
|
84
|
+
(target - current).each do |role_id|
|
|
85
|
+
role = Spree::Role.find_by(id: role_id)
|
|
86
|
+
@resource.role_users.find_or_create_by!(role: role, resource: current_store) if role
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
(current - target).each do |role_id|
|
|
90
|
+
@resource.role_users.where(role_id: role_id, resource: current_store).destroy_all
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class AllowedOriginsController < ResourceController
|
|
6
|
+
scoped_resource :settings
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def model_class
|
|
11
|
+
Spree::AllowedOrigin
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serializer_class
|
|
15
|
+
Spree.api.admin_allowed_origin_serializer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def permitted_params
|
|
19
|
+
params.permit(:origin)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class ApiKeysController < ResourceController
|
|
6
|
+
scoped_resource :settings
|
|
7
|
+
|
|
8
|
+
# PATCH /api/v3/admin/api_keys/:id/revoke
|
|
9
|
+
# Marks the key revoked rather than deleting it — the row stays so
|
|
10
|
+
# audit logs and `created_by`/`revoked_by` remain queryable. Hard
|
|
11
|
+
# deletion is available via `destroy` for cleanup.
|
|
12
|
+
def revoke
|
|
13
|
+
@resource = find_resource
|
|
14
|
+
authorize!(:update, @resource)
|
|
15
|
+
|
|
16
|
+
@resource.revoke!(try_spree_current_user)
|
|
17
|
+
render json: serialize_resource(@resource)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
def model_class
|
|
23
|
+
Spree::ApiKey
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def serializer_class
|
|
27
|
+
Spree.api.admin_api_key_serializer
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def scope
|
|
31
|
+
current_store.api_keys.accessible_by(current_ability, :show)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Stamp the creating user; key generation (token, prefix, digest)
|
|
35
|
+
# happens in `before_validation :generate_token` on the model.
|
|
36
|
+
def build_resource
|
|
37
|
+
scope.new(permitted_params).tap do |key|
|
|
38
|
+
key.created_by = try_spree_current_user
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# `key_type` is set on create only — flipping a publishable key to a
|
|
43
|
+
# secret one (or vice versa) would invalidate every consumer of the
|
|
44
|
+
# plaintext token. We strip it on update so that's impossible to do
|
|
45
|
+
# by accident through the API.
|
|
46
|
+
def permitted_params
|
|
47
|
+
attrs = params.permit(:name, :key_type, scopes: [])
|
|
48
|
+
attrs.delete(:key_type) if action_name == 'update'
|
|
49
|
+
attrs
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class AuthController < Admin::BaseController
|
|
6
|
+
include Spree::Api::V3::Admin::AuthCookies
|
|
7
|
+
|
|
8
|
+
skip_scope_check!
|
|
9
|
+
|
|
10
|
+
rate_limit to: Spree::Api::Config[:rate_limit_login], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :create, with: RATE_LIMIT_RESPONSE
|
|
11
|
+
rate_limit to: Spree::Api::Config[:rate_limit_refresh], within: Spree::Api::Config[:rate_limit_window].seconds, store: Rails.cache, only: :refresh, with: RATE_LIMIT_RESPONSE
|
|
12
|
+
|
|
13
|
+
skip_before_action :authenticate_admin!, only: [:create, :refresh, :logout]
|
|
14
|
+
|
|
15
|
+
# POST /api/v3/admin/auth/login
|
|
16
|
+
def create
|
|
17
|
+
strategy = authentication_strategy
|
|
18
|
+
return unless strategy
|
|
19
|
+
|
|
20
|
+
result = strategy.authenticate
|
|
21
|
+
|
|
22
|
+
if result.success?
|
|
23
|
+
user = result.value
|
|
24
|
+
refresh_token = Spree::RefreshToken.create_for(user, request_env: request_env_for_token)
|
|
25
|
+
set_refresh_cookie(refresh_token)
|
|
26
|
+
render json: auth_response(user)
|
|
27
|
+
else
|
|
28
|
+
render_error(
|
|
29
|
+
code: ERROR_CODES[:authentication_failed],
|
|
30
|
+
message: result.error,
|
|
31
|
+
status: :unauthorized
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# POST /api/v3/admin/auth/refresh
|
|
37
|
+
def refresh
|
|
38
|
+
refresh_token_value = refresh_token_from_cookie
|
|
39
|
+
|
|
40
|
+
if refresh_token_value.blank?
|
|
41
|
+
return render_error(
|
|
42
|
+
code: ERROR_CODES[:invalid_refresh_token],
|
|
43
|
+
message: 'Refresh token cookie missing',
|
|
44
|
+
status: :unauthorized
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
refresh_token = Spree::RefreshToken.active.find_by(token: refresh_token_value)
|
|
49
|
+
|
|
50
|
+
if refresh_token.nil?
|
|
51
|
+
clear_refresh_cookie
|
|
52
|
+
return render_error(
|
|
53
|
+
code: ERROR_CODES[:invalid_refresh_token],
|
|
54
|
+
message: 'Invalid or expired refresh token',
|
|
55
|
+
status: :unauthorized
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
user = refresh_token.user
|
|
60
|
+
new_refresh_token = refresh_token.rotate!(request_env: request_env_for_token)
|
|
61
|
+
set_refresh_cookie(new_refresh_token)
|
|
62
|
+
|
|
63
|
+
render json: auth_response(user)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# POST /api/v3/admin/auth/logout
|
|
67
|
+
def logout
|
|
68
|
+
refresh_token_value = refresh_token_from_cookie
|
|
69
|
+
Spree::RefreshToken.active.find_by(token: refresh_token_value)&.destroy if refresh_token_value.present?
|
|
70
|
+
|
|
71
|
+
clear_refresh_cookie
|
|
72
|
+
head :no_content
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def authentication_strategy
|
|
78
|
+
provider = params[:provider].presence || 'email'
|
|
79
|
+
strategy_class = Spree.admin_authentication_strategies[provider]
|
|
80
|
+
|
|
81
|
+
unless strategy_class
|
|
82
|
+
render_error(
|
|
83
|
+
code: ERROR_CODES[:invalid_provider],
|
|
84
|
+
message: "Unsupported authentication provider: #{provider}",
|
|
85
|
+
status: :bad_request
|
|
86
|
+
)
|
|
87
|
+
return nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
strategy_class.new(
|
|
91
|
+
params: params,
|
|
92
|
+
request_env: request.headers.env,
|
|
93
|
+
user_class: Spree.admin_user_class
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def serializer_params
|
|
98
|
+
{
|
|
99
|
+
store: current_store,
|
|
100
|
+
locale: current_locale,
|
|
101
|
+
currency: current_currency,
|
|
102
|
+
user: current_user,
|
|
103
|
+
includes: []
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def auth_response(user)
|
|
108
|
+
{
|
|
109
|
+
token: generate_jwt(user, audience: JWT_AUDIENCE_ADMIN),
|
|
110
|
+
user: admin_user_serializer.new(user, params: serializer_params).to_h
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def request_env_for_token
|
|
115
|
+
{
|
|
116
|
+
ip_address: request.remote_ip,
|
|
117
|
+
user_agent: request.user_agent&.truncate(255)
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def admin_user_serializer
|
|
122
|
+
Spree.api.admin_admin_user_serializer
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Admin tokens have higher blast radius than customer tokens, so they get a
|
|
126
|
+
# shorter TTL (5 min by default) — overrides the storefront default (1h).
|
|
127
|
+
def jwt_expiration
|
|
128
|
+
Spree::Api::Config[:admin_jwt_expiration]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -3,24 +3,10 @@ module Spree
|
|
|
3
3
|
module V3
|
|
4
4
|
module Admin
|
|
5
5
|
class BaseController < Spree::Api::V3::BaseController
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
include Spree::Api::V3::AdminAuthentication
|
|
7
|
+
include Spree::Api::V3::ScopedAuthorization
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
after_action :set_no_store_cache
|
|
11
|
-
|
|
12
|
-
protected
|
|
13
|
-
|
|
14
|
-
# Override JWT audience to require admin tokens
|
|
15
|
-
def expected_audience
|
|
16
|
-
JWT_AUDIENCE_ADMIN
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def set_no_store_cache
|
|
22
|
-
response.headers['Cache-Control'] = 'private, no-store'
|
|
23
|
-
end
|
|
9
|
+
before_action :authenticate_admin!
|
|
24
10
|
end
|
|
25
11
|
end
|
|
26
12
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class CategoriesController < ResourceController
|
|
6
|
+
scoped_resource :categories
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def model_class
|
|
11
|
+
Spree::Category
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serializer_class
|
|
15
|
+
Spree.api.admin_category_serializer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def scope
|
|
19
|
+
super.where(taxonomy: current_store.taxonomies)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class ChannelsController < ResourceController
|
|
6
|
+
scoped_resource :settings
|
|
7
|
+
|
|
8
|
+
# POST /api/v3/admin/channels/:id/add_products
|
|
9
|
+
# Body: { product_ids: [...], published_at: nil, unpublished_at: nil }
|
|
10
|
+
def add_products
|
|
11
|
+
channel = find_resource
|
|
12
|
+
authorize! :update, channel
|
|
13
|
+
|
|
14
|
+
count = channel.add_products(
|
|
15
|
+
scoped_product_ids,
|
|
16
|
+
published_at: params[:published_at].presence,
|
|
17
|
+
unpublished_at: params[:unpublished_at].presence
|
|
18
|
+
)
|
|
19
|
+
render json: { product_count: count }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# POST /api/v3/admin/channels/:id/remove_products
|
|
23
|
+
# Body: { product_ids: [...] }
|
|
24
|
+
def remove_products
|
|
25
|
+
channel = find_resource
|
|
26
|
+
authorize! :update, channel
|
|
27
|
+
|
|
28
|
+
removed = channel.remove_products(scoped_product_ids)
|
|
29
|
+
render json: { product_count: removed }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def model_class
|
|
35
|
+
Spree::Channel
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def serializer_class
|
|
39
|
+
Spree.api.admin_channel_serializer
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def scope
|
|
43
|
+
super.for_store(current_store)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def permitted_params
|
|
47
|
+
params.permit(:name, :code, :active, :default, :preferred_order_routing_strategy)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# Deliberately not scoped through `for_store` — this endpoint is the
|
|
53
|
+
# path by which a product *enters* the store, so requiring an
|
|
54
|
+
# existing publication would block first-time onboarding. The
|
|
55
|
+
# cross-store guard runs one level up: `find_resource` already
|
|
56
|
+
# restricted the channel to `current_store`.
|
|
57
|
+
def scoped_product_ids
|
|
58
|
+
ids = decode_prefixed_ids(params[:product_ids])
|
|
59
|
+
Spree::Product.accessible_by(current_ability, :update).where(id: ids).ids
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class CountriesController < ResourceController
|
|
6
|
+
scoped_resource :settings
|
|
7
|
+
|
|
8
|
+
# Override base index to skip pagination — there are ~250 countries
|
|
9
|
+
# and address-form dropdowns need them all at once. Pagy's global
|
|
10
|
+
# max_limit (100) prevents using the paginated path for this.
|
|
11
|
+
def index
|
|
12
|
+
authorize!(:read, model_class)
|
|
13
|
+
@collection = scope
|
|
14
|
+
render json: { data: serialize_collection(@collection), meta: { count: @collection.size } }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def model_class
|
|
20
|
+
Spree::Country
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serializer_class
|
|
24
|
+
Spree.api.admin_country_serializer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def scope
|
|
28
|
+
Spree::Country.all.order(:name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_resource
|
|
32
|
+
scope.find_by!(iso: params[:id].upcase)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -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 :custom_field_definitions
|
|
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
|