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,33 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin CRUD for `Spree::GiftCard`. Scoped to the current store via
|
|
6
|
+
# the model's `SingleStoreResource` include — the base controller's
|
|
7
|
+
# `scope` already applies `model_class.for_store(current_store)`.
|
|
8
|
+
#
|
|
9
|
+
# `store` and `created_by` are auto-stamped by `build_resource` in
|
|
10
|
+
# `Spree::Api::V3::ResourceController`, so create requests only need
|
|
11
|
+
# to include user-facing attributes (amount, currency, expires_at,
|
|
12
|
+
# optional code, optional user_id).
|
|
13
|
+
class GiftCardsController < ResourceController
|
|
14
|
+
scoped_resource :gift_cards
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def model_class
|
|
19
|
+
Spree::GiftCard
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def serializer_class
|
|
23
|
+
Spree.api.admin_gift_card_serializer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def permitted_params
|
|
27
|
+
params.permit(:code, :amount, :expires_at, :user_id, :currency)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Public invitation acceptance — mounted under `/api/v3/admin/auth/...`
|
|
6
|
+
# so the issued refresh-token cookie's path matches `/auth/refresh`.
|
|
7
|
+
class InvitationAcceptancesController < BaseController
|
|
8
|
+
include Spree::Api::V3::Admin::AuthCookies
|
|
9
|
+
|
|
10
|
+
skip_scope_check!
|
|
11
|
+
skip_before_action :authenticate_admin!, only: [:lookup, :accept]
|
|
12
|
+
|
|
13
|
+
rate_limit to: Spree::Api::Config[:rate_limit_login],
|
|
14
|
+
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
15
|
+
store: Rails.cache,
|
|
16
|
+
only: [:lookup, :accept],
|
|
17
|
+
with: RATE_LIMIT_RESPONSE
|
|
18
|
+
|
|
19
|
+
# GET /api/v3/admin/auth/invitations/:id/lookup?token=:token
|
|
20
|
+
def lookup
|
|
21
|
+
return unless load_invitation
|
|
22
|
+
|
|
23
|
+
render json: Spree.api.admin_invitation_serializer.new(@invitation).serializable_hash
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# POST /api/v3/admin/auth/invitations/:id/accept?token=:token
|
|
27
|
+
# Body: { password?, password_confirmation?, first_name?, last_name? }
|
|
28
|
+
def accept
|
|
29
|
+
return unless load_invitation
|
|
30
|
+
|
|
31
|
+
user = resolve_or_create_invitee(@invitation)
|
|
32
|
+
return if performed?
|
|
33
|
+
|
|
34
|
+
@invitation.invitee = user
|
|
35
|
+
@invitation.accept!
|
|
36
|
+
|
|
37
|
+
refresh_token = Spree::RefreshToken.create_for(user, request_env: request_env_for_token)
|
|
38
|
+
set_refresh_cookie(refresh_token)
|
|
39
|
+
render json: auth_response(user)
|
|
40
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
41
|
+
render_validation_error(e.record.errors)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Token mismatch is treated identically to "not found" to avoid
|
|
47
|
+
# leaking whether an ID exists.
|
|
48
|
+
def load_invitation
|
|
49
|
+
decoded_id = Spree::Invitation.decode_prefixed_id(params[:id])
|
|
50
|
+
@invitation = Spree::Invitation.pending.not_expired.find_by(id: decoded_id, token: params[:token])
|
|
51
|
+
|
|
52
|
+
unless @invitation
|
|
53
|
+
render_error(
|
|
54
|
+
code: ERROR_CODES[:record_not_found],
|
|
55
|
+
message: 'Invitation not found, expired, or already accepted',
|
|
56
|
+
status: :not_found
|
|
57
|
+
)
|
|
58
|
+
return false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Email match between the invitation and any existing account is
|
|
65
|
+
# implicit: we look the user up by `invitation.email`, never by a
|
|
66
|
+
# client-supplied email. The token is the credential.
|
|
67
|
+
def resolve_or_create_invitee(invitation)
|
|
68
|
+
existing = Spree.admin_user_class.find_by(email: invitation.email)
|
|
69
|
+
return authenticate_existing(existing) if existing
|
|
70
|
+
|
|
71
|
+
create_new_invitee(invitation)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def authenticate_existing(user)
|
|
75
|
+
return user if user.valid_password?(params[:password].to_s)
|
|
76
|
+
|
|
77
|
+
render_error(
|
|
78
|
+
code: ERROR_CODES[:authentication_failed],
|
|
79
|
+
message: 'Invalid password',
|
|
80
|
+
status: :unauthorized
|
|
81
|
+
)
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_new_invitee(invitation)
|
|
86
|
+
if params[:password].blank?
|
|
87
|
+
render_error(
|
|
88
|
+
code: ERROR_CODES[:parameter_missing],
|
|
89
|
+
message: 'Password is required to create your account',
|
|
90
|
+
status: :unprocessable_content
|
|
91
|
+
)
|
|
92
|
+
return nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
Spree.admin_user_class.create!(signup_params(invitation))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def signup_params(invitation)
|
|
99
|
+
params.permit(:password, :password_confirmation, :first_name, :last_name).
|
|
100
|
+
merge(email: invitation.email)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def auth_response(user)
|
|
104
|
+
{
|
|
105
|
+
token: generate_jwt(user, audience: JWT_AUDIENCE_ADMIN),
|
|
106
|
+
user: admin_user_serializer.new(user, params: serializer_params).to_h
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def serializer_params
|
|
111
|
+
{
|
|
112
|
+
store: @invitation&.store || current_store,
|
|
113
|
+
locale: current_locale,
|
|
114
|
+
currency: current_currency,
|
|
115
|
+
user: nil,
|
|
116
|
+
includes: []
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def admin_user_serializer
|
|
121
|
+
Spree.api.admin_admin_user_serializer
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def request_env_for_token
|
|
125
|
+
{
|
|
126
|
+
ip_address: request.remote_ip,
|
|
127
|
+
user_agent: request.user_agent&.truncate(255)
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def jwt_expiration
|
|
132
|
+
Spree::Api::Config[:admin_jwt_expiration]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Manages staff invitations for the current store. Each invitation
|
|
6
|
+
# carries an email + role; on accept, a `Spree::RoleUser` is created
|
|
7
|
+
# via the invitation's `after_accept` callback and the invitee
|
|
8
|
+
# becomes a member of the staff list for this store.
|
|
9
|
+
class InvitationsController < ResourceController
|
|
10
|
+
scoped_resource :settings
|
|
11
|
+
|
|
12
|
+
# PATCH /api/v3/admin/invitations/:id/resend
|
|
13
|
+
# Issues a fresh token + email for an existing pending invitation.
|
|
14
|
+
# The model's `resend!` is responsible for resetting `expires_at`
|
|
15
|
+
# and dispatching the mailer.
|
|
16
|
+
def resend
|
|
17
|
+
@resource = find_resource
|
|
18
|
+
authorize!(:update, @resource)
|
|
19
|
+
|
|
20
|
+
@resource.resend!
|
|
21
|
+
render json: serialize_resource(@resource)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Invitations are immutable post-create — UI calls `resend` for
|
|
25
|
+
# token rotation, `destroy` to revoke. Clearing the action set
|
|
26
|
+
# keeps the surface honest if a client ever fires PATCH directly.
|
|
27
|
+
def update
|
|
28
|
+
head :method_not_allowed
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def model_class
|
|
34
|
+
Spree::Invitation
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serializer_class
|
|
38
|
+
Spree.api.admin_invitation_serializer
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def collection_includes
|
|
42
|
+
[:role, :inviter]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def scope
|
|
46
|
+
Spree::Invitation.
|
|
47
|
+
where(resource: current_store).
|
|
48
|
+
accessible_by(current_ability, :show)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_resource
|
|
52
|
+
scope.new(permitted_params).tap do |invitation|
|
|
53
|
+
invitation.resource = current_store
|
|
54
|
+
invitation.inviter = try_spree_current_user
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# `email` and `role_id` are flat — `role_id` accepts a prefixed ID.
|
|
59
|
+
def permitted_params
|
|
60
|
+
attrs = params.permit(:email, :role_id)
|
|
61
|
+
if attrs[:role_id].present? && Spree::PrefixedId.prefixed_id?(attrs[:role_id])
|
|
62
|
+
attrs[:role_id] = Spree::PrefixedId.decode_prefixed_id(attrs[:role_id])
|
|
63
|
+
end
|
|
64
|
+
attrs
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
# Admin Markets surface. Markets are store-scoped (`store_id` column +
|
|
6
|
+
# `acts_as_list scope: :store_id`), so the base ResourceController's
|
|
7
|
+
# `scope` chain narrows appropriately when we restrict to
|
|
8
|
+
# `current_store.markets`.
|
|
9
|
+
#
|
|
10
|
+
# `country_isos` and `supported_locales` are accepted as arrays on the
|
|
11
|
+
# wire and translated by model setters (`Spree::Market#country_isos=`,
|
|
12
|
+
# `#supported_locales=`).
|
|
13
|
+
class MarketsController < ResourceController
|
|
14
|
+
scoped_resource :settings
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def model_class
|
|
19
|
+
Spree::Market
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def serializer_class
|
|
23
|
+
Spree.api.admin_market_serializer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def collection_includes
|
|
27
|
+
[:countries]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def permitted_params
|
|
31
|
+
normalize_params(
|
|
32
|
+
params.permit(
|
|
33
|
+
:name, :currency, :default_locale, :tax_inclusive,
|
|
34
|
+
:default, :position, supported_locales: [], country_isos: []
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class MeController < Admin::BaseController
|
|
6
|
+
skip_scope_check!
|
|
7
|
+
|
|
8
|
+
# GET /api/v3/admin/me
|
|
9
|
+
# Returns the current admin user along with a serialized representation
|
|
10
|
+
# of their permissions (derived from CanCanCan rules). The SPA uses
|
|
11
|
+
# the permissions list to decide which UI elements to show or hide.
|
|
12
|
+
# The actual authorization check is still enforced server-side by
|
|
13
|
+
# CanCanCan — the SPA list is purely for UX.
|
|
14
|
+
def show
|
|
15
|
+
render json: {
|
|
16
|
+
user: admin_user_serializer.new(current_user, params: serializer_params).to_h,
|
|
17
|
+
permissions: serialize_permissions(current_ability)
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Serializes CanCanCan's rules into a flat, JSON-safe list of permission rules.
|
|
24
|
+
#
|
|
25
|
+
# - Rule order is preserved so the frontend matcher can apply
|
|
26
|
+
# CanCanCan's "last matching rule wins" semantics.
|
|
27
|
+
# - Per-record conditions are NOT serialized (they often reference
|
|
28
|
+
# scopes or blocks that don't translate to JSON). The frontend
|
|
29
|
+
# receives `has_conditions: true` as a hint that the action might
|
|
30
|
+
# be denied at the per-record level — in practice the SPA shows
|
|
31
|
+
# the action optimistically and handles 403 from the API.
|
|
32
|
+
def serialize_permissions(ability)
|
|
33
|
+
ability.send(:rules).map do |rule|
|
|
34
|
+
{
|
|
35
|
+
allow: rule.base_behavior,
|
|
36
|
+
actions: Array(rule.actions).map(&:to_s),
|
|
37
|
+
subjects: Array(rule.subjects).map { |s| s.is_a?(Class) ? s.name : s.to_s },
|
|
38
|
+
has_conditions: rule_has_conditions?(rule)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def rule_has_conditions?(rule)
|
|
44
|
+
return true if rule.block.present?
|
|
45
|
+
conditions = rule.conditions
|
|
46
|
+
return false if conditions.nil?
|
|
47
|
+
return !conditions.empty? if conditions.respond_to?(:empty?)
|
|
48
|
+
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def admin_user_serializer
|
|
53
|
+
Spree.api.admin_admin_user_serializer
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def serializer_params
|
|
57
|
+
{
|
|
58
|
+
store: current_store,
|
|
59
|
+
locale: current_locale,
|
|
60
|
+
currency: current_currency,
|
|
61
|
+
user: current_user,
|
|
62
|
+
includes: []
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class MediaController < ResourceController
|
|
6
|
+
scoped_resource :products
|
|
7
|
+
|
|
8
|
+
def create
|
|
9
|
+
if permitted_params[:url].present?
|
|
10
|
+
create_from_url
|
|
11
|
+
elsif permitted_params[:signed_id].present?
|
|
12
|
+
create_from_signed_id
|
|
13
|
+
else
|
|
14
|
+
@resource = build_resource
|
|
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
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def model_class
|
|
28
|
+
Spree::Asset
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def serializer_class
|
|
32
|
+
Spree.api.admin_media_serializer
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_parent
|
|
36
|
+
@product = current_store.products.find_by_prefix_id!(params[:product_id])
|
|
37
|
+
authorize!(:show, @product)
|
|
38
|
+
|
|
39
|
+
@parent = if params[:variant_id].present?
|
|
40
|
+
@product.variants_including_master.find_by_prefix_id!(params[:variant_id])
|
|
41
|
+
else
|
|
42
|
+
@product
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Variants store assets via the polymorphic `images` association; products own
|
|
47
|
+
# their gallery via `media`. Both resolve to `Spree::Asset` rows with different
|
|
48
|
+
# `viewable_type` values.
|
|
49
|
+
def parent_association
|
|
50
|
+
params[:variant_id].present? ? :images : :media
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# For product-scoped listings we surface BOTH product-level assets and any
|
|
54
|
+
# legacy master-pinned assets, so existing data keeps showing up while
|
|
55
|
+
# merchants migrate. New uploads land on `Spree::Product` (see #set_parent).
|
|
56
|
+
def scope
|
|
57
|
+
return super if params[:variant_id].present?
|
|
58
|
+
|
|
59
|
+
Spree::Asset.where(
|
|
60
|
+
viewable_type: 'Spree::Product', viewable_id: @product.id
|
|
61
|
+
).or(
|
|
62
|
+
Spree::Asset.where(
|
|
63
|
+
viewable_type: 'Spree::Variant', viewable_id: @product.master&.id
|
|
64
|
+
)
|
|
65
|
+
).order(:position)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
ALLOWED_MEDIA_TYPES = -> { [Spree::Asset, *Spree::Asset.descendants].map(&:name).to_set.freeze }
|
|
69
|
+
|
|
70
|
+
def build_resource
|
|
71
|
+
media_type = permitted_params[:type] || 'Spree::Image'
|
|
72
|
+
|
|
73
|
+
unless ALLOWED_MEDIA_TYPES.call.include?(media_type)
|
|
74
|
+
raise ArgumentError, "Invalid media type: #{media_type}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
media = @parent.send(parent_association).build(permitted_params.except(:type, :url, :signed_id))
|
|
78
|
+
media.type = media_type
|
|
79
|
+
|
|
80
|
+
media
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def permitted_params
|
|
84
|
+
params.permit(:type, :alt, :position, :attachment, :url, :signed_id, variant_ids: [])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_from_url
|
|
88
|
+
authorize!(:create, Spree::Asset)
|
|
89
|
+
|
|
90
|
+
url = permitted_params[:url]
|
|
91
|
+
position = permitted_params[:position]
|
|
92
|
+
|
|
93
|
+
Spree::Images::SaveFromUrlJob.perform_later(
|
|
94
|
+
@parent.id,
|
|
95
|
+
@parent.class.name,
|
|
96
|
+
url,
|
|
97
|
+
nil,
|
|
98
|
+
position
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
head :accepted
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def create_from_signed_id
|
|
105
|
+
@resource = build_resource
|
|
106
|
+
@resource.attachment.attach(permitted_params[:signed_id])
|
|
107
|
+
authorize_resource!(@resource, :create)
|
|
108
|
+
|
|
109
|
+
if @resource.save
|
|
110
|
+
render json: serialize_resource(@resource), status: :created
|
|
111
|
+
else
|
|
112
|
+
render_validation_error(@resource.errors)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
class OptionTypesController < ResourceController
|
|
6
|
+
scoped_resource :products
|
|
7
|
+
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def model_class
|
|
11
|
+
Spree::OptionType
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serializer_class
|
|
15
|
+
Spree.api.admin_option_type_serializer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def scope_includes
|
|
19
|
+
[:option_values]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def permitted_params
|
|
23
|
+
params.permit(
|
|
24
|
+
:name, :label, :position, :filterable, :kind,
|
|
25
|
+
option_values: [
|
|
26
|
+
:id, :name, :label, :position, :color_code, :image
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Orders
|
|
6
|
+
class AdjustmentsController < BaseController
|
|
7
|
+
scoped_resource :orders
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def model_class
|
|
12
|
+
Spree::Adjustment
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serializer_class
|
|
16
|
+
Spree.api.admin_adjustment_serializer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parent_association
|
|
20
|
+
:adjustments
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Orders
|
|
6
|
+
class BaseController < ResourceController
|
|
7
|
+
include Spree::Api::V3::OrderLock
|
|
8
|
+
|
|
9
|
+
before_action :authorize_order_access!
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def set_parent
|
|
14
|
+
@parent = current_store.orders.find_by_prefix_id!(params[:order_id])
|
|
15
|
+
@order = @parent
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def authorize_order_access!
|
|
19
|
+
authorize!(:show, @parent)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module V3
|
|
4
|
+
module Admin
|
|
5
|
+
module Orders
|
|
6
|
+
class FulfillmentsController < BaseController
|
|
7
|
+
scoped_resource :fulfillments
|
|
8
|
+
|
|
9
|
+
before_action :set_resource, only: [:show, :update, :fulfill, :cancel, :resume, :split]
|
|
10
|
+
|
|
11
|
+
# PATCH /api/v3/admin/orders/:order_id/fulfillments/:id
|
|
12
|
+
def update
|
|
13
|
+
with_order_lock do
|
|
14
|
+
result = Spree.shipment_update_service.call(
|
|
15
|
+
shipment: @resource,
|
|
16
|
+
shipment_attributes: permitted_params.to_h
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if result.success?
|
|
20
|
+
render json: serialize_resource(@resource.reload)
|
|
21
|
+
else
|
|
22
|
+
render_result_error(result)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/fulfill
|
|
28
|
+
def fulfill
|
|
29
|
+
with_order_lock do
|
|
30
|
+
@resource.ship!
|
|
31
|
+
render json: serialize_resource(@resource.reload)
|
|
32
|
+
rescue StateMachines::InvalidTransition => e
|
|
33
|
+
render_service_error(e.message)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/cancel
|
|
38
|
+
def cancel
|
|
39
|
+
with_order_lock do
|
|
40
|
+
@resource.cancel!
|
|
41
|
+
render json: serialize_resource(@resource.reload)
|
|
42
|
+
rescue StateMachines::InvalidTransition => e
|
|
43
|
+
render_service_error(e.message)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/resume
|
|
48
|
+
def resume
|
|
49
|
+
with_order_lock do
|
|
50
|
+
@resource.resume!
|
|
51
|
+
render json: serialize_resource(@resource.reload)
|
|
52
|
+
rescue StateMachines::InvalidTransition => e
|
|
53
|
+
render_service_error(e.message)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# PATCH /api/v3/admin/orders/:order_id/fulfillments/:id/split
|
|
58
|
+
def split
|
|
59
|
+
with_order_lock do
|
|
60
|
+
variant = Spree::Variant.find_by_prefix_id!(params[:variant_id])
|
|
61
|
+
quantity = params[:quantity].to_i
|
|
62
|
+
|
|
63
|
+
stock_location = if params[:stock_location_id].present?
|
|
64
|
+
Spree::StockLocation.find_by_prefix_id!(params[:stock_location_id])
|
|
65
|
+
else
|
|
66
|
+
@resource.stock_location
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
fulfilment_changer = @resource.transfer_to_location(variant, quantity, stock_location)
|
|
70
|
+
|
|
71
|
+
if fulfilment_changer.run!
|
|
72
|
+
fulfillments = @order.reload.shipments
|
|
73
|
+
render json: {
|
|
74
|
+
data: fulfillments.map { |s| serialize_resource(s) }
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
render_validation_error(fulfilment_changer.errors)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
protected
|
|
83
|
+
|
|
84
|
+
def model_class
|
|
85
|
+
Spree::Shipment
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def serializer_class
|
|
89
|
+
Spree.api.admin_fulfillment_serializer
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def parent_association
|
|
93
|
+
:shipments
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def permitted_params
|
|
97
|
+
params.permit(:tracking, :selected_shipping_rate_id, :stock_location_id, :state)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|