spree_api 5.4.3 → 5.5.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +36 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +62 -0
  4. data/app/controllers/concerns/spree/api/v3/admin/role_grant_guard.rb +52 -0
  5. data/app/controllers/concerns/spree/api/v3/admin/subclassed_resource.rb +149 -0
  6. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +54 -0
  7. data/app/controllers/concerns/spree/api/v3/bulk_operations.rb +103 -0
  8. data/app/controllers/concerns/spree/api/v3/channel_resolution.rb +60 -0
  9. data/app/controllers/concerns/spree/api/v3/error_handler.rb +4 -0
  10. data/app/controllers/concerns/spree/api/v3/params_normalizer.rb +84 -0
  11. data/app/controllers/concerns/spree/api/v3/scoped_authorization.rb +104 -0
  12. data/app/controllers/concerns/spree/api/v3/store/search_provider_support.rb +35 -1
  13. data/app/controllers/spree/api/v3/admin/admin_users_controller.rb +109 -0
  14. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +25 -0
  15. data/app/controllers/spree/api/v3/admin/api_keys_controller.rb +84 -0
  16. data/app/controllers/spree/api/v3/admin/auth_controller.rb +134 -0
  17. data/app/controllers/spree/api/v3/admin/base_controller.rb +3 -17
  18. data/app/controllers/spree/api/v3/admin/categories_controller.rb +25 -0
  19. data/app/controllers/spree/api/v3/admin/channels_controller.rb +65 -0
  20. data/app/controllers/spree/api/v3/admin/countries_controller.rb +38 -0
  21. data/app/controllers/spree/api/v3/admin/coupon_codes_controller.rb +33 -0
  22. data/app/controllers/spree/api/v3/admin/custom_field_definitions_controller.rb +34 -0
  23. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +129 -0
  24. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +31 -0
  25. data/app/controllers/spree/api/v3/admin/customers/addresses_controller.rb +83 -0
  26. data/app/controllers/spree/api/v3/admin/customers/base_controller.rb +33 -0
  27. data/app/controllers/spree/api/v3/admin/customers/credit_cards_controller.rb +25 -0
  28. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +92 -0
  29. data/app/controllers/spree/api/v3/admin/customers_controller.rb +119 -0
  30. data/app/controllers/spree/api/v3/admin/dashboard_controller.rb +44 -0
  31. data/app/controllers/spree/api/v3/admin/direct_uploads_controller.rb +40 -0
  32. data/app/controllers/spree/api/v3/admin/exports_controller.rb +136 -0
  33. data/app/controllers/spree/api/v3/admin/gift_card_batches_controller.rb +31 -0
  34. data/app/controllers/spree/api/v3/admin/gift_cards_controller.rb +33 -0
  35. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +138 -0
  36. data/app/controllers/spree/api/v3/admin/invitations_controller.rb +81 -0
  37. data/app/controllers/spree/api/v3/admin/markets_controller.rb +42 -0
  38. data/app/controllers/spree/api/v3/admin/me_controller.rb +69 -0
  39. data/app/controllers/spree/api/v3/admin/media_controller.rb +119 -0
  40. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +34 -0
  41. data/app/controllers/spree/api/v3/admin/orders/adjustments_controller.rb +27 -0
  42. data/app/controllers/spree/api/v3/admin/orders/base_controller.rb +31 -0
  43. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +104 -0
  44. data/app/controllers/spree/api/v3/admin/orders/gift_cards_controller.rb +79 -0
  45. data/app/controllers/spree/api/v3/admin/orders/items_controller.rb +92 -0
  46. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +90 -0
  47. data/app/controllers/spree/api/v3/admin/orders/refunds_controller.rb +53 -0
  48. data/app/controllers/spree/api/v3/admin/orders/store_credits_controller.rb +59 -0
  49. data/app/controllers/spree/api/v3/admin/orders_controller.rb +190 -0
  50. data/app/controllers/spree/api/v3/admin/payment_methods_controller.rb +73 -0
  51. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +179 -0
  52. data/app/controllers/spree/api/v3/admin/prices_controller.rb +157 -0
  53. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +48 -0
  54. data/app/controllers/spree/api/v3/admin/products_controller.rb +237 -0
  55. data/app/controllers/spree/api/v3/admin/promotion_actions_controller.rb +78 -0
  56. data/app/controllers/spree/api/v3/admin/promotion_rules_controller.rb +56 -0
  57. data/app/controllers/spree/api/v3/admin/promotions_controller.rb +78 -0
  58. data/app/controllers/spree/api/v3/admin/resource_controller.rb +29 -11
  59. data/app/controllers/spree/api/v3/admin/roles_controller.rb +29 -0
  60. data/app/controllers/spree/api/v3/admin/stock_items_controller.rb +35 -0
  61. data/app/controllers/spree/api/v3/admin/stock_locations_controller.rb +36 -0
  62. data/app/controllers/spree/api/v3/admin/stock_reservations_controller.rb +29 -0
  63. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +75 -0
  64. data/app/controllers/spree/api/v3/admin/store_controller.rb +53 -0
  65. data/app/controllers/spree/api/v3/admin/store_credit_categories_controller.rb +21 -0
  66. data/app/controllers/spree/api/v3/admin/tags_controller.rb +51 -0
  67. data/app/controllers/spree/api/v3/admin/tax_categories_controller.rb +21 -0
  68. data/app/controllers/spree/api/v3/admin/variants_controller.rb +33 -0
  69. data/app/controllers/spree/api/v3/admin/webhook_deliveries_controller.rb +49 -0
  70. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +75 -0
  71. data/app/controllers/spree/api/v3/resource_controller.rb +117 -8
  72. data/app/controllers/spree/api/v3/store/auth_controller.rb +8 -28
  73. data/app/controllers/spree/api/v3/store/base_controller.rb +6 -0
  74. data/app/controllers/spree/api/v3/store/carts_controller.rb +1 -0
  75. data/app/controllers/spree/api/v3/store/customers_controller.rb +6 -0
  76. data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +77 -0
  77. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +2 -2
  78. data/app/controllers/spree/api/v3/store/products_controller.rb +4 -3
  79. data/app/controllers/spree/api/v3/store/resource_controller.rb +10 -2
  80. data/app/jobs/spree/webhook_delivery_job.rb +5 -0
  81. data/app/models/spree/api_key_ability.rb +16 -0
  82. data/app/serializers/spree/api/v3/admin/address_serializer.rb +2 -6
  83. data/app/serializers/spree/api/v3/admin/adjustment_serializer.rb +3 -15
  84. data/app/serializers/spree/api/v3/admin/admin_user_serializer.rb +19 -3
  85. data/app/serializers/spree/api/v3/admin/allowed_origin_serializer.rb +2 -6
  86. data/app/serializers/spree/api/v3/admin/api_key_serializer.rb +42 -0
  87. data/app/serializers/spree/api/v3/admin/category_serializer.rb +4 -3
  88. data/app/serializers/spree/api/v3/admin/channel_serializer.rb +15 -0
  89. data/app/serializers/spree/api/v3/admin/country_serializer.rb +1 -1
  90. data/app/serializers/spree/api/v3/admin/coupon_code_serializer.rb +30 -0
  91. data/app/serializers/spree/api/v3/admin/credit_card_serializer.rb +4 -2
  92. data/app/serializers/spree/api/v3/admin/custom_field_definition_serializer.rb +21 -0
  93. data/app/serializers/spree/api/v3/admin/custom_field_serializer.rb +8 -3
  94. data/app/serializers/spree/api/v3/admin/customer_group_serializer.rb +27 -0
  95. data/app/serializers/spree/api/v3/admin/customer_serializer.rb +58 -2
  96. data/app/serializers/spree/api/v3/admin/dashboard_analytics_serializer.rb +143 -0
  97. data/app/serializers/spree/api/v3/admin/export_serializer.rb +40 -0
  98. data/app/serializers/spree/api/v3/admin/fulfillment_serializer.rb +2 -6
  99. data/app/serializers/spree/api/v3/admin/{asset_serializer.rb → gift_card_batch_serializer.rb} +1 -1
  100. data/app/serializers/spree/api/v3/admin/gift_card_serializer.rb +39 -4
  101. data/app/serializers/spree/api/v3/admin/invitation_serializer.rb +64 -0
  102. data/app/serializers/spree/api/v3/admin/line_item_serializer.rb +4 -16
  103. data/app/serializers/spree/api/v3/admin/media_serializer.rb +24 -2
  104. data/app/serializers/spree/api/v3/admin/option_type_serializer.rb +4 -1
  105. data/app/serializers/spree/api/v3/admin/option_value_serializer.rb +4 -1
  106. data/app/serializers/spree/api/v3/admin/order_serializer.rb +21 -6
  107. data/app/serializers/spree/api/v3/admin/payment_method_serializer.rb +11 -2
  108. data/app/serializers/spree/api/v3/admin/payment_serializer.rb +2 -6
  109. data/app/serializers/spree/api/v3/admin/payment_source_serializer.rb +4 -1
  110. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +51 -0
  111. data/app/serializers/spree/api/v3/admin/price_rule_serializer.rb +55 -0
  112. data/app/serializers/spree/api/v3/admin/price_serializer.rb +4 -0
  113. data/app/serializers/spree/api/v3/admin/product_publication_serializer.rb +11 -0
  114. data/app/serializers/spree/api/v3/admin/product_serializer.rb +34 -10
  115. data/app/serializers/spree/api/v3/admin/promotion_action_serializer.rb +71 -0
  116. data/app/serializers/spree/api/v3/admin/promotion_rule_serializer.rb +85 -0
  117. data/app/serializers/spree/api/v3/admin/promotion_serializer.rb +41 -0
  118. data/app/serializers/spree/api/v3/admin/refund_serializer.rb +4 -2
  119. data/app/serializers/spree/api/v3/admin/role_serializer.rb +17 -0
  120. data/app/serializers/spree/api/v3/admin/stock_item_serializer.rb +16 -1
  121. data/app/serializers/spree/api/v3/admin/stock_location_serializer.rb +11 -2
  122. data/app/serializers/spree/api/v3/admin/stock_reservation_serializer.rb +46 -0
  123. data/app/serializers/spree/api/v3/admin/stock_transfer_serializer.rb +37 -0
  124. data/app/serializers/spree/api/v3/admin/store_credit_category_serializer.rb +19 -0
  125. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +11 -5
  126. data/app/serializers/spree/api/v3/admin/store_serializer.rb +55 -0
  127. data/app/serializers/spree/api/v3/admin/tax_category_serializer.rb +4 -2
  128. data/app/serializers/spree/api/v3/admin/variant_serializer.rb +37 -6
  129. data/app/serializers/spree/api/v3/admin/webhook_delivery_serializer.rb +45 -0
  130. data/app/serializers/spree/api/v3/admin/webhook_endpoint_serializer.rb +69 -0
  131. data/app/serializers/spree/api/v3/channel_serializer.rb +14 -0
  132. data/app/serializers/spree/api/v3/custom_field_serializer.rb +9 -10
  133. data/app/serializers/spree/api/v3/customer_serializer.rb +5 -0
  134. data/app/serializers/spree/api/v3/market_serializer.rb +2 -1
  135. data/app/serializers/spree/api/v3/media_serializer.rb +8 -6
  136. data/app/serializers/spree/api/v3/order_serializer.rb +6 -1
  137. data/app/serializers/spree/api/v3/payment_method_serializer.rb +11 -2
  138. data/app/serializers/spree/api/v3/product_publication_serializer.rb +22 -0
  139. data/app/serializers/spree/api/v3/product_serializer.rb +6 -1
  140. data/app/serializers/spree/api/v3/stock_reservation_serializer.rb +10 -0
  141. data/config/locales/en.yml +2 -0
  142. data/config/routes.rb +235 -1
  143. data/lib/spree/api/configuration.rb +2 -2
  144. data/lib/spree/api/dependencies.rb +25 -1
  145. data/lib/spree/api/openapi/path_sorter.rb +126 -0
  146. data/lib/spree/api/openapi/schema_helper.rb +185 -6
  147. data/lib/spree/api/testing_support/v3/base.rb +28 -0
  148. metadata +98 -8
  149. data/app/serializers/spree/api/v3/admin/shipping_category_serializer.rb +0 -14
@@ -0,0 +1,77 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Store
5
+ class NewsletterSubscribersController < Store::BaseController
6
+ rate_limit to: Spree::Api::Config[:rate_limit_register],
7
+ within: Spree::Api::Config[:rate_limit_window].seconds,
8
+ store: Rails.cache,
9
+ only: [:create, :verify],
10
+ with: RATE_LIMIT_RESPONSE
11
+
12
+ # POST /api/v3/store/newsletter_subscribers
13
+ def create
14
+ subscriber = Spree::NewsletterSubscriber.subscribe(
15
+ email: params[:email],
16
+ user: current_user,
17
+ store: current_store,
18
+ redirect_url: validated_redirect_url
19
+ )
20
+
21
+ if subscriber.errors.any?
22
+ render_errors(subscriber.errors)
23
+ else
24
+ render json: serialize_resource(subscriber), status: :created
25
+ end
26
+ end
27
+
28
+ # POST /api/v3/store/newsletter_subscribers/verify
29
+ def verify
30
+ token = params[:token]
31
+
32
+ if token.blank?
33
+ return render_error(
34
+ code: ERROR_CODES[:parameter_missing],
35
+ message: 'token is required',
36
+ status: :unprocessable_content
37
+ )
38
+ end
39
+
40
+ subscriber = Spree::NewsletterSubscriber.for_store(current_store).unverified.find_by(verification_token: token)
41
+
42
+ unless subscriber
43
+ return render_error(
44
+ code: ERROR_CODES[:invalid_token],
45
+ message: Spree.t(:newsletter_verification_token_invalid, scope: :api),
46
+ status: :unprocessable_content
47
+ )
48
+ end
49
+
50
+ Spree::Newsletter::Verify.new(subscriber: subscriber).call
51
+
52
+ render json: serialize_resource(subscriber)
53
+ end
54
+
55
+ protected
56
+
57
+ def serializer_class
58
+ Spree::Api::V3::NewsletterSubscriberSerializer
59
+ end
60
+
61
+ private
62
+
63
+ # Drop redirect_url when it isn't in the store's allow-list — secure-by-default,
64
+ # mirrors password_resets. Returning nil omits it from the webhook payload rather
65
+ # than rejecting the request, so callers can't probe the allow-list via 4xx errors.
66
+ def validated_redirect_url
67
+ redirect_url = params[:redirect_url]
68
+ return nil if redirect_url.blank?
69
+ return nil unless current_store.allowed_origin?(redirect_url)
70
+
71
+ redirect_url
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -29,7 +29,7 @@ module Spree
29
29
 
30
30
  def filters_cache_key
31
31
  products_table = Spree::Product.table_name
32
- stats = current_store.products.active(current_currency)
32
+ stats = current_store.products.available(Time.current, current_currency)
33
33
  .pick(Arel.sql("MAX(#{products_table}.updated_at)"), Arel.sql("COUNT(DISTINCT #{products_table}.id)"))
34
34
  max_updated = stats&.first&.to_i
35
35
  product_count = stats&.last || 0
@@ -50,7 +50,7 @@ module Spree
50
50
  end
51
51
 
52
52
  def filters_scope
53
- scope = current_store.products.active(current_currency)
53
+ scope = current_store.products.available(Time.current, current_currency)
54
54
  scope = scope.in_category(category) if category.present?
55
55
  scope.accessible_by(current_ability, :show)
56
56
  end
@@ -29,15 +29,16 @@ module Spree
29
29
  end
30
30
 
31
31
  def scope
32
- super.active(Spree::Current.currency)
32
+ super.available(Time.current, Spree::Current.currency)
33
33
  end
34
34
 
35
35
  # these scopes are not automatically picked by ar_lazy_preload gem and we need to explicitly include them
36
36
  def scope_includes
37
37
  [
38
+ product_publications: [],
38
39
  primary_media: [attachment_attachment: :blob],
39
- master: [:prices, stock_items: :stock_location],
40
- variants: [:prices, stock_items: :stock_location]
40
+ master: [:prices, stock_items: [:stock_location, :active_stock_reservations]],
41
+ variants: [:prices, stock_items: [:stock_location, :active_stock_reservations]]
41
42
  ]
42
43
  end
43
44
 
@@ -2,9 +2,17 @@ module Spree
2
2
  module Api
3
3
  module V3
4
4
  module Store
5
+ # Mirrors Store::BaseController's concerns. Both classes anchor parallel
6
+ # inheritance branches (V3::BaseController vs V3::ResourceController);
7
+ # any concern added here MUST also be added to Store::BaseController.
5
8
  class ResourceController < Spree::Api::V3::ResourceController
6
- # Require publishable API key for all Store API requests
7
- before_action :authenticate_api_key!
9
+ include Spree::Api::V3::ChannelResolution
10
+
11
+ protected
12
+
13
+ def authenticate_request!
14
+ authenticate_api_key!
15
+ end
8
16
  end
9
17
  end
10
18
  end
@@ -4,7 +4,12 @@ module Spree
4
4
  class WebhookDeliveryJob < Spree::BaseJob
5
5
  queue_as Spree.queues.webhooks
6
6
 
7
+ # Webhook delivery hits external endpoints; broad retry covers network timeouts,
8
+ # 5xx, DNS failures, etc.
7
9
  retry_on StandardError, wait: :polynomially_longer, attempts: 5
10
+ # Must come after `retry_on StandardError` so DeserializationError lands in discard
11
+ # (ActiveJob handler lookup is reverse-declaration-order).
12
+ discard_on ActiveJob::DeserializationError
8
13
 
9
14
  # Accept optional second argument for backward compatibility with jobs
10
15
  # enqueued before this change was deployed.
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ # CanCanCan ability used for API-key-authenticated admin requests.
3
+ # Grants full access — authorization happens at the scope-check layer
4
+ # (Spree::Api::V3::ScopedAuthorization), not at the per-record CanCanCan
5
+ # layer. This exists so that `accessible_by(current_ability, :show)` in
6
+ # admin controllers returns the unrestricted scope (it would otherwise
7
+ # require a real Spree::Ability with role lookups, which doesn't apply
8
+ # to API key principals).
9
+ class ApiKeyAbility
10
+ include CanCan::Ability
11
+
12
+ def initialize(_options = {})
13
+ can :manage, :all
14
+ end
15
+ end
16
+ end
@@ -5,18 +5,14 @@ module Spree
5
5
  class AddressSerializer < V3::AddressSerializer
6
6
  typelize label: [:string, nullable: true],
7
7
  customer_id: [:string, nullable: true],
8
- metadata: 'Record<string, unknown> | null'
8
+ metadata: 'Record<string, unknown>'
9
9
 
10
- attributes :label,
10
+ attributes :label, :metadata,
11
11
  created_at: :iso8601, updated_at: :iso8601
12
12
 
13
13
  attribute :customer_id do |address|
14
14
  address.user&.prefixed_id
15
15
  end
16
-
17
- attribute :metadata do |address|
18
- address.metadata.presence
19
- end
20
16
  end
21
17
  end
22
18
  end
@@ -4,31 +4,19 @@ module Spree
4
4
  module Admin
5
5
  class AdjustmentSerializer < V3::BaseSerializer
6
6
  typelize label: :string, amount: :string, display_amount: :string,
7
- state: :string, eligible: :boolean, mandatory: :boolean, included: :boolean,
8
- source_type: [:string, nullable: true],
9
- adjustable_type: :string, adjustable_id: :string,
10
- order_id: [:string, nullable: true],
11
- source_id: [:string, nullable: true]
7
+ included: :boolean,
8
+ order_id: [:string, nullable: true]
12
9
 
13
- attributes :label, :display_amount, :state, :eligible, :mandatory, :included,
14
- :source_type, :adjustable_type,
10
+ attributes :label, :display_amount, :included,
15
11
  created_at: :iso8601, updated_at: :iso8601
16
12
 
17
13
  attribute :amount do |adjustment|
18
14
  adjustment.amount.to_s
19
15
  end
20
16
 
21
- attribute :adjustable_id do |adjustment|
22
- adjustment.adjustable&.prefixed_id
23
- end
24
-
25
17
  attribute :order_id do |adjustment|
26
18
  adjustment.order&.prefixed_id
27
19
  end
28
-
29
- attribute :source_id do |adjustment|
30
- adjustment.source&.prefixed_id
31
- end
32
20
  end
33
21
  end
34
22
  end
@@ -3,11 +3,27 @@ module Spree
3
3
  module V3
4
4
  module Admin
5
5
  class AdminUserSerializer < V3::BaseSerializer
6
- typelize email: :string, first_name: [:string, nullable: true],
7
- last_name: [:string, nullable: true]
6
+ typelize email: :string,
7
+ first_name: [:string, nullable: true],
8
+ last_name: [:string, nullable: true],
9
+ full_name: [:string, nullable: true],
10
+ roles: 'Array<{ id: string; name: string }>'
8
11
 
9
- attributes :email, :first_name, :last_name,
12
+ attributes :email, :first_name, :last_name, :full_name,
10
13
  created_at: :iso8601, updated_at: :iso8601
14
+
15
+ # Roles assigned to this user *for the current store*. Each store
16
+ # gets its own role set via `Spree::RoleUser`, so this attribute is
17
+ # scoped against `current_store` rather than returning every role
18
+ # the user might have on other stores. Block receives `params`
19
+ # only when Alba passes it through the `serializer_params` hash —
20
+ # we fall back to `Spree::Current.store` if not.
21
+ attribute :roles do |user, params|
22
+ store = params&.dig(:store) || Spree::Current.store
23
+ scope = user.role_users
24
+ scope = scope.where(resource: store) if store
25
+ scope.includes(:role).map { |ru| { id: ru.role.prefixed_id, name: ru.role.name } }
26
+ end
11
27
  end
12
28
  end
13
29
  end
@@ -5,13 +5,9 @@ module Spree
5
5
  module V3
6
6
  module Admin
7
7
  class AllowedOriginSerializer < V3::BaseSerializer
8
- typelize store_id: :string
8
+ typelize origin: :string
9
9
 
10
- attributes :id, :origin, created_at: :iso8601, updated_at: :iso8601
11
-
12
- attribute :store_id do |allowed_origin|
13
- allowed_origin.store&.prefixed_id
14
- end
10
+ attributes :origin, created_at: :iso8601, updated_at: :iso8601
15
11
  end
16
12
  end
17
13
  end
@@ -0,0 +1,42 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API serializer for {Spree::ApiKey}.
6
+ #
7
+ # Never exposes `token` or `token_digest` — only the 12-char
8
+ # `token_prefix` (e.g. `sk_abc123def`) so existing keys can be
9
+ # identified in the UI without leaking material that would let an
10
+ # attacker make requests. The full plaintext token is delivered
11
+ # exactly once, as the response body of `POST /api/v3/admin/api_keys`,
12
+ # via {#plaintext_token} below — it is `nil` everywhere else.
13
+ class ApiKeySerializer < V3::BaseSerializer
14
+ typelize name: :string,
15
+ key_type: :string,
16
+ token_prefix: [:string, nullable: true],
17
+ plaintext_token: [:string, nullable: true],
18
+ scopes: [:string, multi: true],
19
+ revoked_at: [:string, nullable: true],
20
+ last_used_at: [:string, nullable: true],
21
+ created_by_email: [:string, nullable: true]
22
+
23
+ attributes :name, :key_type, :token_prefix, :scopes,
24
+ created_at: :iso8601, updated_at: :iso8601,
25
+ revoked_at: :iso8601, last_used_at: :iso8601
26
+
27
+ # Returned only on the create response — `plaintext_token` is held in
28
+ # memory on the model after `generate_token` and is never persisted
29
+ # for secret keys, so we serialize it whenever it's available rather
30
+ # than gating on the action.
31
+ attribute :plaintext_token do |key|
32
+ key.plaintext_token
33
+ end
34
+
35
+ attribute :created_by_email do |key|
36
+ key.created_by&.email
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -5,10 +5,11 @@ module Spree
5
5
  # Admin API Category Serializer
6
6
  # Full category data including admin-only fields
7
7
  class CategorySerializer < V3::CategorySerializer
8
- typelize lft: :number, rgt: :number
8
+ typelize pretty_name: :string, lft: :number, rgt: :number, sort_order: :string,
9
+ metadata: 'Record<string, unknown>'
9
10
 
10
- # Nested set columns for tree operations
11
- attributes :lft, :rgt, created_at: :iso8601, updated_at: :iso8601
11
+ # Admin-only attributes
12
+ attributes :metadata, :pretty_name, :lft, :rgt, created_at: :iso8601, updated_at: :iso8601
12
13
 
13
14
  # Override inherited associations to use admin serializers
14
15
  one :parent,
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ class ChannelSerializer < V3::ChannelSerializer
6
+ typelize store_id: :string,
7
+ preferred_order_routing_strategy: [:string, nullable: true]
8
+
9
+ attributes :preferred_order_routing_strategy,
10
+ created_at: :iso8601, updated_at: :iso8601
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -7,7 +7,7 @@ module Spree
7
7
 
8
8
  many :states,
9
9
  resource: Spree.api.admin_state_serializer,
10
- if: proc { expand?(:states) }
10
+ if: proc { params[:expand]&.include?('states') }
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Api
5
+ module V3
6
+ module Admin
7
+ # Coupon codes belong to multi-code promotions. Read-only here:
8
+ # codes are generated server-side based on the promotion's
9
+ # `code_prefix` + `number_of_codes`.
10
+ class CouponCodeSerializer < BaseSerializer
11
+ typelize code: :string,
12
+ state: [:string, nullable: true],
13
+ promotion_id: :string,
14
+ order_id: [:string, nullable: true]
15
+
16
+ attributes :code, :state,
17
+ created_at: :iso8601, updated_at: :iso8601
18
+
19
+ attribute :promotion_id do |coupon|
20
+ coupon.promotion&.prefixed_id
21
+ end
22
+
23
+ attribute :order_id do |coupon|
24
+ coupon.order&.prefixed_id
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -4,7 +4,8 @@ module Spree
4
4
  module Admin
5
5
  class CreditCardSerializer < V3::CreditCardSerializer
6
6
  typelize customer_id: [:string, nullable: true],
7
- payment_method_id: [:string, nullable: true]
7
+ payment_method_id: [:string, nullable: true],
8
+ metadata: 'Record<string, unknown>'
8
9
 
9
10
  attribute :customer_id do |credit_card|
10
11
  credit_card.user&.prefixed_id
@@ -14,7 +15,8 @@ module Spree
14
15
  credit_card.payment_method&.prefixed_id
15
16
  end
16
17
 
17
- attributes created_at: :iso8601, updated_at: :iso8601
18
+ attributes :metadata,
19
+ created_at: :iso8601, updated_at: :iso8601
18
20
  end
19
21
  end
20
22
  end
@@ -0,0 +1,21 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Admin API Custom Field Definition Serializer
6
+ # Schema-side metadata for custom fields (per resource type).
7
+ class CustomFieldDefinitionSerializer < BaseSerializer
8
+ typelize namespace: :string,
9
+ key: :string,
10
+ label: :string,
11
+ field_type: Spree::Metafield::FIELD_TYPE_TOKENS,
12
+ resource_type: :string,
13
+ storefront_visible: :boolean
14
+
15
+ attributes :namespace, :key, :label, :field_type, :resource_type, :storefront_visible,
16
+ created_at: :iso8601, updated_at: :iso8601
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -5,12 +5,17 @@ module Spree
5
5
  # Admin API Custom Field Serializer
6
6
  # Full custom field data including admin-only fields
7
7
  class CustomFieldSerializer < V3::CustomFieldSerializer
8
- typelize storefront_visible: :boolean
8
+ typelize storefront_visible: :boolean,
9
+ custom_field_definition_id: :string
9
10
 
10
11
  attributes created_at: :iso8601, updated_at: :iso8601
11
12
 
12
- attribute :storefront_visible do |metafield|
13
- metafield.display_on.in?(%w[both front_end])
13
+ attribute :storefront_visible do |custom_field|
14
+ custom_field.metafield_definition.available_on_front_end?
15
+ end
16
+
17
+ attribute :custom_field_definition_id do |custom_field|
18
+ custom_field.metafield_definition.prefixed_id
14
19
  end
15
20
  end
16
21
  end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Admin
5
+ # Serializes Spree::CustomerGroup for the admin pickers
6
+ # (e.g. promotion rule, customer-group filters). Surfaces only
7
+ # what the admin UI needs to display + select rows.
8
+ class CustomerGroupSerializer < V3::BaseSerializer
9
+ typelize name: :string,
10
+ description: 'string | null',
11
+ customers_count: :number
12
+
13
+ attributes :name, :description, :customers_count,
14
+ created_at: :iso8601, updated_at: :iso8601
15
+
16
+ # Members are paginated separately via `/customers?customer_group_id_in=…`
17
+ # because a group can hold tens of thousands of users — embedding the
18
+ # whole list on every group fetch would explode the index payload.
19
+ # Pass `expand=customers` when you need them inline (single-record reads only).
20
+ many :customers,
21
+ resource: Spree.api.admin_customer_serializer,
22
+ if: proc { expand?('customers') }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -8,10 +8,19 @@ module Spree
8
8
  typelize login: [:string, nullable: true],
9
9
  last_sign_in_at: [:string, nullable: true], current_sign_in_at: [:string, nullable: true],
10
10
  sign_in_count: :number, failed_attempts: :number,
11
- last_sign_in_ip: [:string, nullable: true], current_sign_in_ip: [:string, nullable: true]
11
+ last_sign_in_ip: [:string, nullable: true], current_sign_in_ip: [:string, nullable: true],
12
+ tags: [:string, multi: true],
13
+ internal_note_html: [:string, nullable: true],
14
+ metadata: 'Record<string, unknown>',
15
+ orders_count: :number,
16
+ total_spent: :string,
17
+ display_total_spent: :string,
18
+ last_order_completed_at: [:string, nullable: true],
19
+ default_billing_address_id: [:string, nullable: true],
20
+ default_shipping_address_id: [:string, nullable: true]
12
21
 
13
22
  # Admin-only attributes
14
- attributes :login,
23
+ attributes :login, :metadata,
15
24
  last_sign_in_at: :iso8601, current_sign_in_at: :iso8601,
16
25
  created_at: :iso8601, updated_at: :iso8601
17
26
 
@@ -31,6 +40,45 @@ module Spree
31
40
  user.current_sign_in_ip
32
41
  end
33
42
 
43
+ attribute :tags do |user|
44
+ user.tag_list.to_a
45
+ end
46
+
47
+ attribute :internal_note_html do |user|
48
+ user.respond_to?(:internal_note) ? user.internal_note&.body&.to_s.presence : nil
49
+ end
50
+
51
+ attribute :default_billing_address_id do |user|
52
+ user.bill_address&.prefixed_id
53
+ end
54
+
55
+ attribute :default_shipping_address_id do |user|
56
+ user.ship_address&.prefixed_id
57
+ end
58
+
59
+ # Order aggregates: prefer attributes precomputed on the scope (see
60
+ # CustomersController#scope) to avoid N+1 on list endpoints. Fall
61
+ # back to per-user queries when not preloaded (e.g. show endpoint
62
+ # for a freshly loaded record).
63
+ attribute :orders_count do |user|
64
+ user.attributes['orders_count']&.to_i || user.orders.complete.count
65
+ end
66
+
67
+ attribute :total_spent do |user|
68
+ (user.attributes['total_spent'] || user.orders.complete.sum(:total)).to_s
69
+ end
70
+
71
+ attribute :display_total_spent do |user|
72
+ amount = user.attributes['total_spent'] || user.orders.complete.sum(:total)
73
+ currency = Spree::Current.currency || Spree::Current.store&.default_currency || Spree::Config[:currency]
74
+ Spree::Money.new(amount, currency: currency).to_s
75
+ end
76
+
77
+ attribute :last_order_completed_at do |user|
78
+ value = user.attributes.key?('last_order_completed_at') ? user.attributes['last_order_completed_at'] : user.orders.complete.maximum(:completed_at)
79
+ value.respond_to?(:iso8601) ? value.iso8601 : value
80
+ end
81
+
34
82
  # Override inherited associations to use admin serializers
35
83
  many :addresses, resource: Spree.api.admin_address_serializer, if: proc { expand?('addresses') }
36
84
  one :bill_address, key: :default_billing_address, resource: Spree.api.admin_address_serializer, if: proc { expand?('default_billing_address') }
@@ -39,6 +87,14 @@ module Spree
39
87
  many :orders,
40
88
  resource: Spree.api.admin_order_serializer,
41
89
  if: proc { expand?('orders') }
90
+
91
+ many :store_credits,
92
+ resource: Spree.api.admin_store_credit_serializer,
93
+ if: proc { expand?('store_credits') }
94
+
95
+ many :customer_groups,
96
+ resource: Spree.api.admin_customer_group_serializer,
97
+ if: proc { expand?('customer_groups') }
42
98
  end
43
99
  end
44
100
  end