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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +19 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -0
  4. data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +149 -0
  5. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +54 -0
  6. data/app/controllers/concerns/spree/api/v3/bulk_operations.rb +103 -0
  7. data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +60 -0
  8. data/app/controllers/concerns/spree/api/v3/error_handler.rb +4 -0
  9. data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +84 -0
  10. data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +88 -0
  11. data/app/controllers/concerns/spree/api/v3/store/search_provider_support.rb +35 -1
  12. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +97 -0
  13. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +25 -0
  14. data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +55 -0
  15. data/app/controllers/spree/api/v3/admin/auth_controller.rb +134 -0
  16. data/app/controllers/spree/api/v3/admin/base_controller.rb +3 -17
  17. data/app/controllers/spree/api/v3/admin/categories_controller.rb +25 -0
  18. data/app/controllers/spree/api/v3/admin/channels_controller.rb +65 -0
  19. data/app/controllers/spree/api/v3/admin/countries_controller.rb +38 -0
  20. data/app/controllers/spree/api/v3/admin/coupon_codes_controller.rb +33 -0
  21. data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +34 -0
  22. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +108 -0
  23. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +31 -0
  24. data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +88 -0
  25. data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +31 -0
  26. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +93 -0
  27. data/app/controllers/spree/api/v3/admin/customers_controller.rb +119 -0
  28. data/app/controllers/spree/api/v3/admin/dashboard_controller.rb +44 -0
  29. data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +40 -0
  30. data/app/controllers/spree/api/v3/admin/exports_controller.rb +89 -0
  31. data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +31 -0
  32. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +33 -0
  33. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +138 -0
  34. data/app/controllers/spree/api/v3/admin/invitations_controller.rb +70 -0
  35. data/app/controllers/spree/api/v3/admin/markets_controller.rb +42 -0
  36. data/app/controllers/spree/api/v3/admin/me_controller.rb +69 -0
  37. data/app/controllers/spree/api/v3/admin/media_controller.rb +119 -0
  38. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +34 -0
  39. data/app/controllers/spree/api/v3/admin/orders/adjustments_controller.rb +27 -0
  40. data/app/controllers/spree/api/v3/admin/orders/base_controller.rb +26 -0
  41. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +104 -0
  42. data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +79 -0
  43. data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +92 -0
  44. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +90 -0
  45. data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +53 -0
  46. data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +59 -0
  47. data/app/controllers/spree/api/v3/admin/orders_controller.rb +190 -0
  48. data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +73 -0
  49. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +156 -0
  50. data/app/controllers/spree/api/v3/admin/prices_controller.rb +129 -0
  51. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +48 -0
  52. data/app/controllers/spree/api/v3/admin/products_controller.rb +237 -0
  53. data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +78 -0
  54. data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +56 -0
  55. data/app/controllers/spree/api/v3/admin/promotions_controller.rb +78 -0
  56. data/app/controllers/spree/api/v3/admin/resource_controller.rb +29 -11
  57. data/app/controllers/spree/api/v3/admin/roles_controller.rb +29 -0
  58. data/app/controllers/spree/api/v3/admin/stock_items_controller.rb +35 -0
  59. data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +36 -0
  60. data/app/controllers/spree/api/v3/admin/stock_reservations_controller.rb +29 -0
  61. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +75 -0
  62. data/app/controllers/spree/api/v3/admin/store_controller.rb +53 -0
  63. data/app/controllers/spree/api/v3/admin/store_credit_categories_controller.rb +21 -0
  64. data/app/controllers/spree/api/v3/admin/tags_controller.rb +51 -0
  65. data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +21 -0
  66. data/app/controllers/spree/api/v3/admin/variants_controller.rb +33 -0
  67. data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +49 -0
  68. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +75 -0
  69. data/app/controllers/spree/api/v3/resource_controller.rb +90 -8
  70. data/app/controllers/spree/api/v3/store/auth_controller.rb +8 -28
  71. data/app/controllers/spree/api/v3/store/base_controller.rb +6 -0
  72. data/app/controllers/spree/api/v3/store/carts_controller.rb +1 -0
  73. data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -0
  74. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +77 -0
  75. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +2 -2
  76. data/app/controllers/spree/api/v3/store/products_controller.rb +3 -3
  77. data/app/controllers/spree/api/v3/store/resource_controller.rb +10 -2
  78. data/app/jobs/spree/webhook_delivery_job.rb +5 -0
  79. data/app/models/spree/api_key_ability.rb +16 -0
  80. data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -6
  81. data/app/serializers/spree/api/v3/admin/adjustment_serializer.rb +3 -15
  82. data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +19 -3
  83. data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +2 -6
  84. data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +42 -0
  85. data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
  86. data/app/serializers/spree/api/v3/admin/channel_serializer.rb +15 -0
  87. data/app/serializers/spree/api/v3/admin/country_serializer.rb +1 -1
  88. data/app/serializers/spree/api/v3/admin/coupon_code_serializer.rb +30 -0
  89. data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +4 -2
  90. data/app/serializers/spree/api/v3/admin/custom_field_definition_serializer.rb +21 -0
  91. data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +8 -3
  92. data/app/serializers/spree/api/v3/admin/customer_group_serializer.rb +27 -0
  93. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +58 -2
  94. data/app/serializers/spree/api/v3/admin/dashboard_analytics_serializer.rb +143 -0
  95. data/app/serializers/spree/api/v3/admin/export_serializer.rb +40 -0
  96. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -6
  97. data/app/serializers/spree/api/v3/admin/{asset_serializer.rb → gift_card_batch_serializer.rb} +1 -1
  98. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +39 -4
  99. data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +64 -0
  100. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +4 -16
  101. data/app/serializers/spree/api/v3/admin/media_serializer.rb +24 -2
  102. data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +4 -1
  103. data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +4 -1
  104. data/app/serializers/spree/api/v3/admin/order_serializer.rb +21 -6
  105. data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +11 -2
  106. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -6
  107. data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +4 -1
  108. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +51 -0
  109. data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +55 -0
  110. data/app/serializers/spree/api/v3/admin/price_serializer.rb +4 -0
  111. data/app/serializers/spree/api/v3/admin/product_publication_serializer.rb +11 -0
  112. data/app/serializers/spree/api/v3/admin/product_serializer.rb +34 -10
  113. data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +71 -0
  114. data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +85 -0
  115. data/app/serializers/spree/api/v3/admin/promotion_serializer.rb +41 -0
  116. data/app/serializers/spree/api/v3/admin/refund_serializer.rb +4 -2
  117. data/app/serializers/spree/api/v3/admin/role_serializer.rb +17 -0
  118. data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +16 -1
  119. data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +11 -2
  120. data/app/serializers/spree/api/v3/admin/stock_reservation_serializer.rb +46 -0
  121. data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +37 -0
  122. data/app/serializers/spree/api/v3/admin/store_credit_category_serializer.rb +19 -0
  123. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +11 -5
  124. data/app/serializers/spree/api/v3/admin/store_serializer.rb +55 -0
  125. data/app/serializers/spree/api/v3/admin/tax_category_serializer.rb +4 -2
  126. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +37 -6
  127. data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +45 -0
  128. data/app/serializers/spree/api/v3/admin/webhook_endpoint_serializer.rb +69 -0
  129. data/app/serializers/spree/api/v3/channel_serializer.rb +14 -0
  130. data/app/serializers/spree/api/v3/custom_field_serializer.rb +9 -10
  131. data/app/serializers/spree/api/v3/customer_serializer.rb +5 -0
  132. data/app/serializers/spree/api/v3/market_serializer.rb +2 -1
  133. data/app/serializers/spree/api/v3/media_serializer.rb +8 -6
  134. data/app/serializers/spree/api/v3/order_serializer.rb +6 -1
  135. data/app/serializers/spree/api/v3/payment_method_serializer.rb +11 -2
  136. data/app/serializers/spree/api/v3/product_publication_serializer.rb +22 -0
  137. data/app/serializers/spree/api/v3/product_serializer.rb +6 -1
  138. data/app/serializers/spree/api/v3/stock_reservation_serializer.rb +10 -0
  139. data/config/locales/en.yml +2 -0
  140. data/config/routes.rb +235 -1
  141. data/lib/spree/api/configuration.rb +2 -2
  142. data/lib/spree/api/dependencies.rb +25 -1
  143. data/lib/spree/api/openapi/path_sorter.rb +126 -0
  144. data/lib/spree/api/openapi/schema_helper.rb +185 -6
  145. metadata +96 -8
  146. 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