spree_api 6.0.0.rc5 → 6.0.0.rc7

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/spree/api/v3/admin/auth_cookies.rb +23 -0
  3. data/app/controllers/concerns/spree/api/v3/admin/refund_authorization.rb +26 -0
  4. data/app/controllers/concerns/spree/api/v3/admin_authentication.rb +1 -0
  5. data/app/controllers/concerns/spree/api/v3/api_key_authentication.rb +14 -2
  6. data/app/controllers/concerns/spree/api/v3/cart_resolvable.rb +15 -2
  7. data/app/controllers/concerns/spree/api/v3/digital_assets_resource.rb +68 -0
  8. data/app/controllers/concerns/spree/api/v3/error_handler.rb +3 -0
  9. data/app/controllers/concerns/spree/api/v3/jwt_authentication.rb +27 -1
  10. data/app/controllers/concerns/spree/api/v3/orders/fulfillment_actions.rb +6 -1
  11. data/app/controllers/spree/api/v3/admin/allowed_origins_controller.rb +4 -1
  12. data/app/controllers/spree/api/v3/admin/auth_controller.rb +32 -5
  13. data/app/controllers/spree/api/v3/admin/commission_lines_controller.rb +2 -2
  14. data/app/controllers/spree/api/v3/admin/custom_fields_controller.rb +4 -7
  15. data/app/controllers/spree/api/v3/admin/customer_groups_controller.rb +4 -0
  16. data/app/controllers/spree/api/v3/admin/customers/store_credits_controller.rb +1 -1
  17. data/app/controllers/spree/api/v3/admin/customers_controller.rb +13 -8
  18. data/app/controllers/spree/api/v3/admin/delivery_profiles/origin_groups_controller.rb +1 -1
  19. data/app/controllers/spree/api/v3/admin/delivery_profiles_controller.rb +1 -1
  20. data/app/controllers/spree/api/v3/admin/digital_links_controller.rb +3 -1
  21. data/app/controllers/spree/api/v3/admin/invitation_acceptances_controller.rb +2 -0
  22. data/app/controllers/spree/api/v3/admin/me_controller.rb +2 -0
  23. data/app/controllers/spree/api/v3/admin/option_types_controller.rb +13 -0
  24. data/app/controllers/spree/api/v3/admin/orders/claims_controller.rb +2 -0
  25. data/app/controllers/spree/api/v3/admin/orders/exchanges_controller.rb +9 -0
  26. data/app/controllers/spree/api/v3/admin/orders/fulfillments_controller.rb +1 -1
  27. data/app/controllers/spree/api/v3/admin/orders/payments_controller.rb +7 -4
  28. data/app/controllers/spree/api/v3/admin/orders/returns_controller.rb +2 -0
  29. data/app/controllers/spree/api/v3/admin/orders_controller.rb +10 -2
  30. data/app/controllers/spree/api/v3/admin/password_resets_controller.rb +7 -1
  31. data/app/controllers/spree/api/v3/admin/price_lists_controller.rb +18 -0
  32. data/app/controllers/spree/api/v3/admin/product_types_controller.rb +12 -1
  33. data/app/controllers/spree/api/v3/admin/products/digital_assets_controller.rb +5 -109
  34. data/app/controllers/spree/api/v3/admin/products/variants_controller.rb +4 -1
  35. data/app/controllers/spree/api/v3/admin/products_controller.rb +28 -3
  36. data/app/controllers/spree/api/v3/admin/sellers/payouts_controller.rb +4 -3
  37. data/app/controllers/spree/api/v3/admin/stock_transfers_controller.rb +3 -2
  38. data/app/controllers/spree/api/v3/admin/store_controller.rb +7 -0
  39. data/app/controllers/spree/api/v3/admin/tags_controller.rb +11 -9
  40. data/app/controllers/spree/api/v3/admin/webhook_endpoints_controller.rb +5 -2
  41. data/app/controllers/spree/api/v3/resource_controller.rb +15 -2
  42. data/app/controllers/spree/api/v3/seller/auth_controller.rb +10 -0
  43. data/app/controllers/spree/api/v3/seller/invitation_acceptances_controller.rb +2 -0
  44. data/app/controllers/spree/api/v3/seller/me_controller.rb +4 -0
  45. data/app/controllers/spree/api/v3/seller/orders_controller.rb +11 -3
  46. data/app/controllers/spree/api/v3/seller/password_resets_controller.rb +5 -1
  47. data/app/controllers/spree/api/v3/seller/products/digital_assets_controller.rb +67 -0
  48. data/app/controllers/spree/api/v3/seller/products_controller.rb +4 -2
  49. data/app/controllers/spree/api/v3/seller/team_controller.rb +1 -1
  50. data/app/controllers/spree/api/v3/store/carts/store_credits_controller.rb +9 -1
  51. data/app/controllers/spree/api/v3/store/carts_controller.rb +14 -0
  52. data/app/controllers/spree/api/v3/store/customers_controller.rb +13 -1
  53. data/app/controllers/spree/api/v3/store/delivery_methods_controller.rb +6 -1
  54. data/app/controllers/spree/api/v3/store/products/filters_controller.rb +19 -4
  55. data/app/request_schemas/spree/api/v3/digital_asset_request_schema.rb +25 -0
  56. data/app/serializers/concerns/spree/api/v3/digital_asset_details.rb +70 -0
  57. data/app/serializers/spree/api/v3/admin/delivery_origin_group_serializer.rb +2 -2
  58. data/app/serializers/spree/api/v3/admin/delivery_profile_serializer.rb +3 -5
  59. data/app/serializers/spree/api/v3/admin/digital_asset_serializer.rb +3 -47
  60. data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +1 -0
  61. data/app/serializers/spree/api/v3/admin/price_history_serializer.rb +1 -1
  62. data/app/serializers/spree/api/v3/admin/price_list_serializer.rb +8 -3
  63. data/app/serializers/spree/api/v3/admin/price_serializer.rb +1 -1
  64. data/app/serializers/spree/api/v3/admin/seller_payout_serializer.rb +2 -2
  65. data/app/serializers/spree/api/v3/admin/seller_serializer.rb +1 -3
  66. data/app/serializers/spree/api/v3/admin/store_credit_serializer.rb +3 -5
  67. data/app/serializers/spree/api/v3/base_serializer.rb +6 -1
  68. data/app/serializers/spree/api/v3/newsletter_subscriber_serializer.rb +5 -1
  69. data/app/serializers/spree/api/v3/seller/digital_asset_serializer.rb +16 -0
  70. data/app/serializers/spree/api/v3/seller/payout_serializer.rb +2 -2
  71. data/app/serializers/spree/api/v3/seller/store_serializer.rb +18 -0
  72. data/app/services/spree/api/v3/filters_aggregator.rb +63 -18
  73. data/config/locales/en.yml +1 -0
  74. data/config/routes.rb +3 -0
  75. data/lib/spree/api/dependencies.rb +2 -2
  76. data/lib/spree/api/engine.rb +6 -0
  77. data/lib/spree/api/middleware/no_index.rb +33 -0
  78. data/lib/spree/api/openapi/schema_helper.rb +2 -1
  79. data/lib/spree/api/testing_support/v3/base.rb +18 -0
  80. metadata +14 -6
@@ -0,0 +1,67 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Seller
5
+ module Products
6
+ # The downloadable files on one of this seller's own products.
7
+ #
8
+ # Uploaded files only: a provider is the operator's own code
9
+ # reaching into their internal systems, so a seller can neither pick
10
+ # one nor read its settings. An asset the operator set up with one
11
+ # is still listed, since it is part of what the seller's product
12
+ # delivers.
13
+ #
14
+ # The marketplace's rules for a seller's file — whether sellers may
15
+ # upload at all, which content types, and whether an approved
16
+ # listing goes back to review — apply because the workflow is told
17
+ # who is writing.
18
+ class DigitalAssetsController < Seller::ResourceController
19
+ include Spree::Api::V3::DigitalAssetsResource
20
+
21
+ scoped_resource :products
22
+
23
+ request_params :digital_asset do |body|
24
+ body.include_attributes Spree::Api::V3::DigitalAssetRequestSchema
25
+ body.require_attributes :signed_id, on: :create
26
+ end
27
+
28
+ # DELETE /api/v3/seller/products/:product_id/digital_assets/:id
29
+ #
30
+ # Removing a file is covered by the same switch as adding one: a
31
+ # marketplace that keeps digital files to itself keeps the ones it
32
+ # attached to a seller's product.
33
+ def destroy
34
+ unless current_store.preferred_seller_digital_assets_enabled
35
+ return render_validation_error(I18n.t('spree.digital_assets_sellers_disabled'))
36
+ end
37
+
38
+ super
39
+ end
40
+
41
+ protected
42
+
43
+ def serializer_class
44
+ Spree.api.seller_digital_asset_serializer
45
+ end
46
+
47
+ def create_workflow_arguments
48
+ super.merge(seller: current_seller, submitted_by: current_user)
49
+ end
50
+
51
+ def update_workflow_arguments
52
+ super.merge(seller: current_seller, submitted_by: current_user)
53
+ end
54
+
55
+ # Rooted in the seller's own catalog, so another seller's product
56
+ # is a 404. Changing a product's files is a change to the product,
57
+ # so writes need `:update` on it, not just `:show`.
58
+ def set_parent
59
+ @parent = current_seller.products.find_by_prefix_id!(params[:product_id])
60
+ authorize_parent!(@parent)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -142,8 +142,10 @@ module Spree
142
142
  submitted_by: try_spree_current_user)
143
143
 
144
144
  # A product Propose refused is untouched; one it accepted is in
145
- # review or already through it.
146
- product.reload.status != was
145
+ # review or already through it. The saved value, not the
146
+ # attribute: a refused update leaves the new status unsaved on
147
+ # the record, and a rolled-back one restores what was stored.
148
+ product.status_in_database != was
147
149
  }
148
150
  end
149
151
 
@@ -86,7 +86,7 @@ module Spree
86
86
  private
87
87
 
88
88
  def members
89
- @members ||= current_seller.users.to_a
89
+ @members ||= current_seller.users.with_attached_avatar.to_a
90
90
  end
91
91
 
92
92
  def set_member
@@ -8,7 +8,7 @@ module Spree
8
8
  include Spree::Api::V3::OrderLock
9
9
 
10
10
  before_action :require_authentication!
11
- before_action :find_cart!
11
+ before_action :find_owned_cart!
12
12
  before_action :stringify_numeric_amount, only: :create
13
13
 
14
14
  request_params 'ApplyStoreCreditParams', action: :create do |body|
@@ -50,6 +50,14 @@ module Spree
50
50
 
51
51
  private
52
52
 
53
+ # The balance spent here is the cart owner's, so a guest token —
54
+ # which only proves the caller holds this cart — is not enough:
55
+ # the signed-in customer must be the owner.
56
+ def find_owned_cart!
57
+ @cart = resolve_cart(include_completed: false)
58
+ authorize_storefront_write!(@cart, token: nil)
59
+ end
60
+
53
61
  # 5.6 storefronts sent a JSON number; read it through its text so no
54
62
  # float reaches the money math.
55
63
  def stringify_numeric_amount
@@ -112,6 +112,7 @@ module Spree
112
112
  # Auto-advances to the next checkout step when possible.
113
113
  def update
114
114
  find_cart!
115
+ authorize_address_book_access!
115
116
 
116
117
  with_order_lock do
117
118
  result = Spree.carts_update_service.call(
@@ -238,6 +239,19 @@ module Spree
238
239
 
239
240
  # Find incomplete cart for associate action.
240
241
  # Only finds guest carts (no user) or carts already owned by current user (idempotent).
242
+ ADDRESS_BOOK_PARAMS = %i[shipping_address billing_address shipping_address_id billing_address_id].freeze
243
+
244
+ # A typed address on a customer's cart is saved into that customer's
245
+ # address book and may become their default, and an address id names
246
+ # an entry of that book. The guest token proves the caller holds the
247
+ # cart, not the account, so those writes need the owner.
248
+ def authorize_address_book_access!
249
+ return if @cart.customer.nil?
250
+ return unless ADDRESS_BOOK_PARAMS.any? { |key| params.key?(key) }
251
+
252
+ authorize_storefront_write!(@cart, token: nil)
253
+ end
254
+
241
255
  def find_cart_for_association
242
256
  current_store.carts.incomplete.where(customer: [nil, current_user]).find_by_prefix_id!(params[:id])
243
257
  end
@@ -31,7 +31,8 @@ module Spree
31
31
 
32
32
  request_params 'UpdateCustomerParams', action: :update do |body|
33
33
  body.attribute :email, :string, format: :email, example: 'customer@example.com'
34
- body.attribute :password, :string
34
+ body.attribute :password, :string,
35
+ description: 'Changing it signs the customer out everywhere: every refresh token is revoked, so sign in again once the access token expires.'
35
36
  body.attribute :password_confirmation, :string
36
37
  body.attribute :current_password, :string,
37
38
  description: 'Required when changing the email or the password.'
@@ -75,6 +76,7 @@ module Spree
75
76
 
76
77
  # GET /api/v3/store/customer
77
78
  def show
79
+ preload_default_addresses
78
80
  render json: serialize_resource(current_user)
79
81
  end
80
82
 
@@ -86,6 +88,10 @@ module Spree
86
88
  current_user.consent_source = Spree::ConsentRecord::ACCOUNT
87
89
 
88
90
  if current_user.update(update_params)
91
+ # A password change is how a customer takes a stolen session
92
+ # back, so no refresh token issued before it may outlive it —
93
+ # the reset flow does the same.
94
+ Spree::RefreshToken.revoke_all_for(current_user) if update_params[:password].present?
89
95
  render json: serialize_resource(current_user)
90
96
  else
91
97
  render_errors(current_user.errors)
@@ -115,6 +121,12 @@ module Spree
115
121
 
116
122
  private
117
123
 
124
+ # Both default addresses live in one table, so they are fetched in one
125
+ # query rather than one each.
126
+ def preload_default_addresses
127
+ ActiveRecord::Associations::Preloader.new(records: [current_user], associations: %i[bill_address ship_address]).call
128
+ end
129
+
118
130
  def sensitive_update?
119
131
  (params[:email].present? && params[:email] != current_user.email) ||
120
132
  params[:password].present?
@@ -93,7 +93,7 @@ module Spree
93
93
  def covers_cart?(location, cart)
94
94
  return true if location.pickup_stock_policy == 'any'
95
95
 
96
- cart.line_items.includes(variant: :product).all? do |line_item|
96
+ pickup_line_items(cart).all? do |line_item|
97
97
  next true if line_item.variant.digital?
98
98
  next true unless line_item.variant.should_track_inventory?
99
99
 
@@ -101,6 +101,11 @@ module Spree
101
101
  stock_level.present? && stock_level.available_count >= line_item.quantity
102
102
  end
103
103
  end
104
+
105
+ # Loaded once for every location the cart is checked against.
106
+ def pickup_line_items(cart)
107
+ @pickup_line_items ||= cart.line_items.includes(variant: [:delivery_profile, { product: %i[delivery_profile store] }]).to_a
108
+ end
104
109
  end
105
110
  end
106
111
  end
@@ -5,6 +5,7 @@ module Spree
5
5
  module Products
6
6
  class FiltersController < Store::BaseController
7
7
  include Spree::Api::V3::Store::SearchProviderSupport
8
+ include Spree::Api::V3::Store::StorefrontProducts
8
9
 
9
10
  def index
10
11
  json = Rails.cache.fetch(filters_cache_key, expires_in: 15.minutes) do
@@ -30,8 +31,8 @@ module Spree
30
31
 
31
32
  def filters_cache_key
32
33
  products_table = Spree::Product.table_name
33
- stats = current_store.products.available(Time.current, current_currency, include_preorderable: true)
34
- .pick(Arel.sql("MAX(#{products_table}.updated_at)"), Arel.sql("COUNT(DISTINCT #{products_table}.id)"))
34
+ stats = storefront_products.
35
+ pick(Arel.sql("MAX(#{products_table}.updated_at)"), Arel.sql("COUNT(DISTINCT #{products_table}.id)"))
35
36
  max_updated = stats&.first&.to_i
36
37
  product_count = stats&.last || 0
37
38
 
@@ -41,6 +42,7 @@ module Spree
41
42
  current_currency,
42
43
  current_locale,
43
44
  (hide_prices? ? 'prices_hidden' : nil),
45
+ buyer_catalog_fragment,
44
46
  category&.cache_key_with_version,
45
47
  collection&.cache_key_with_version,
46
48
  search_query,
@@ -53,11 +55,24 @@ module Spree
53
55
  parts.compact.join('/')
54
56
  end
55
57
 
58
+ # The buyer's catalogs partition the cache: a facet payload built
59
+ # for one assortment must not be served to a buyer who sees
60
+ # another, or it would describe products their listing never
61
+ # returns.
62
+ def buyer_catalog_fragment
63
+ catalogs = Spree::Catalog.for_buyer(store: current_store, customer: current_user, channel: current_channel)
64
+ return if catalogs.empty?
65
+
66
+ # Versioned, since adding or removing a product touches the
67
+ # catalog without touching the product count or timestamps.
68
+ catalogs.map(&:cache_key_with_version).sort.join('/')
69
+ end
70
+
56
71
  def filters_scope
57
72
  # Mirror ProductsController#scope so facet counts describe the
58
73
  # same product set the listing returns (coming-soon pre-orders
59
- # included).
60
- scope = current_store.products.available(Time.current, current_currency, include_preorderable: true)
74
+ # included) — narrowed to the buyer's catalogs like the listing.
75
+ scope = storefront_products
61
76
  scope = scope.in_category(category) if category.present?
62
77
  scope = scope.in_collection(collection) if collection.present?
63
78
  scope
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ # What a downloadable file on a product carries, whichever panel writes
5
+ # it. The operator's endpoint adds the provider fields on top.
6
+ class DigitalAssetRequestSchema < Spree::Api::V3::RequestSchema
7
+ # Names a blob rather than a column, so it is attached separately.
8
+ attribute :signed_id, :string,
9
+ permit: false,
10
+ description: "A `signed_id` from `files.upload()` with `visibility: 'private'`. On update it replaces the file for everyone who already bought it. A seller's file must be of a content type the marketplace accepts, checked against the file's own signature where it has one."
11
+ # Resolved through the parent rather than assigned, so an asset can't
12
+ # be pointed at a variant of another product or store.
13
+ attribute :variant_id, :id,
14
+ permit: false,
15
+ description: "On create, defaults to the product's default variant when omitted. On update, omitting it keeps the current variant."
16
+ attribute :authorized_clicks, :integer,
17
+ nullable: true,
18
+ description: "Null means the store's download settings apply."
19
+ attribute :authorized_days, :integer,
20
+ nullable: true,
21
+ description: "Null means the store's download settings apply."
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ # What a panel shows about a downloadable file: its size, the limits
5
+ # that apply to it, and a short-lived link to check what was uploaded.
6
+ # Shared by the operator's and a seller's serializer, so both panels
7
+ # read the same fields.
8
+ module DigitalAssetDetails
9
+ extend ActiveSupport::Concern
10
+
11
+ # The store whose download settings apply: the request's, or the
12
+ # asset's own outside a request.
13
+ #
14
+ # @param digital_asset [Spree::DigitalAsset]
15
+ # @param params [Hash, nil] the serializer params
16
+ # @return [Spree::Store]
17
+ def self.store_for(digital_asset, params)
18
+ params&.dig(:store) || Spree::Current.store || digital_asset.store
19
+ end
20
+
21
+ included do
22
+ typelize byte_size: [:number, nullable: true],
23
+ authorized_clicks: [:number, nullable: true],
24
+ authorized_days: [:number, nullable: true],
25
+ effective_authorized_clicks: :number,
26
+ effective_authorized_days: :number,
27
+ provider_type: [:string, nullable: true, comment: 'Digital asset provider; null is an uploaded file. Built-in: file. Extensions may register more.'],
28
+ provider_name: [:string, comment: 'Digital asset provider name. Built-in: File. Extensions may register more.'],
29
+ download_url: [:string, nullable: true]
30
+
31
+ attributes :authorized_clicks, :authorized_days, :byte_size
32
+ attributes created_at: :iso8601, updated_at: :iso8601
33
+
34
+ api_type_attributes :provider_type
35
+
36
+ # Human-readable source for the listing. Blank provider_type is the
37
+ # uploaded-file default, so this reads "File" there.
38
+ attribute :provider_name do |digital_asset|
39
+ digital_asset.provider_class.provider_name
40
+ end
41
+
42
+ # Lets a merchant check what they actually uploaded. Short-lived and
43
+ # panel-only: this is the file itself, not a customer's download
44
+ # grant, so it carries no counter and spends nobody's allowance.
45
+ # Nil for a provider-backed asset (no file to preview) and outside a
46
+ # request (the Disk service needs a host only a controller supplies).
47
+ attribute :download_url do |digital_asset, params|
48
+ next nil unless digital_asset.downloadable?
49
+
50
+ store = DigitalAssetDetails.store_for(digital_asset, params)
51
+ begin
52
+ digital_asset.download_url(expires_in: store.preferred_digital_asset_link_expire_time.seconds)
53
+ rescue ArgumentError
54
+ nil
55
+ end
56
+ end
57
+
58
+ # The limits actually applied, after the store-settings fallback.
59
+ attribute :effective_authorized_clicks do |digital_asset, params|
60
+ digital_asset.effective_authorized_clicks(DigitalAssetDetails.store_for(digital_asset, params))
61
+ end
62
+
63
+ attribute :effective_authorized_days do |digital_asset, params|
64
+ digital_asset.effective_authorized_days(DigitalAssetDetails.store_for(digital_asset, params))
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -19,11 +19,11 @@ module Spree
19
19
  end
20
20
 
21
21
  attribute :delivery_zones_count do |record|
22
- record.delivery_zones.count
22
+ record.delivery_zones.size
23
23
  end
24
24
 
25
25
  attribute :delivery_methods_count do |record|
26
- record.delivery_methods.count
26
+ record.delivery_methods.size
27
27
  end
28
28
  end
29
29
  end
@@ -47,16 +47,14 @@ module Spree
47
47
  end
48
48
  end
49
49
 
50
- attribute :products_count do |record|
51
- record.products.count
52
- end
50
+ attribute :products_count, &:products_count
53
51
 
54
52
  attribute :delivery_methods_count do |record|
55
- record.delivery_methods.count
53
+ record.delivery_methods.size
56
54
  end
57
55
 
58
56
  attribute :delivery_zones_count do |record|
59
- record.delivery_zones.count
57
+ record.delivery_zones.size
60
58
  end
61
59
  end
62
60
  end
@@ -5,55 +5,11 @@ module Spree
5
5
  module V3
6
6
  module Admin
7
7
  class DigitalAssetSerializer < V3::DigitalAssetSerializer
8
- typelize byte_size: [:number, nullable: true],
9
- authorized_clicks: [:number, nullable: true],
10
- authorized_days: [:number, nullable: true],
11
- effective_authorized_clicks: :number,
12
- effective_authorized_days: :number,
13
- provider_type: [:string, nullable: true, comment: 'Digital asset provider; null is an uploaded file. Built-in: file. Extensions may register more.'],
14
- provider_name: [:string, comment: 'Digital asset provider name. Built-in: File. Extensions may register more.'],
15
- provider_settings: 'Record<string, unknown>',
16
- download_url: [:string, nullable: true]
8
+ include Spree::Api::V3::DigitalAssetDetails
17
9
 
18
- attributes :authorized_clicks, :authorized_days, :byte_size, :provider_settings
19
- attributes created_at: :iso8601, updated_at: :iso8601
10
+ typelize provider_settings: 'Record<string, unknown>'
20
11
 
21
- api_type_attributes :provider_type
22
-
23
- # Human-readable source for the admin listing. Blank provider_type is
24
- # the uploaded-file default, so this reads "File" there.
25
- attribute :provider_name do |digital_asset|
26
- digital_asset.provider_class.provider_name
27
- end
28
-
29
- # Lets a merchant check what they actually uploaded. Short-lived and
30
- # admin-only: this is the file itself, not a customer's download
31
- # grant, so it carries no counter and spends nobody's allowance.
32
- # Nil for a provider-backed asset (no file to preview) and outside a
33
- # request (the Disk service needs a host only a controller supplies).
34
- attribute :download_url do |digital_asset, params|
35
- next nil unless digital_asset.downloadable?
36
-
37
- store = params&.dig(:store) || Spree::Current.store || digital_asset.store
38
- begin
39
- digital_asset.download_url(expires_in: store.preferred_digital_asset_link_expire_time.seconds)
40
- rescue ArgumentError
41
- nil
42
- end
43
- end
44
-
45
- # The limits actually applied, after the store-settings fallback. The
46
- # request's store answers it — an admin request always carries one;
47
- # the asset's own store covers serialization outside a request.
48
- attribute :effective_authorized_clicks do |digital_asset, params|
49
- store = params&.dig(:store) || Spree::Current.store || digital_asset.store
50
- digital_asset.effective_authorized_clicks(store)
51
- end
52
-
53
- attribute :effective_authorized_days do |digital_asset, params|
54
- store = params&.dig(:store) || Spree::Current.store || digital_asset.store
55
- digital_asset.effective_authorized_days(store)
56
- end
12
+ attributes :provider_settings
57
13
  end
58
14
  end
59
15
  end
@@ -8,6 +8,7 @@ module Spree
8
8
  # Back-office surface, nested under the admin customer serializer. Extends the
9
9
  # customer-facing serializer so public fields stay in sync.
10
10
  class NewsletterSubscriberSerializer < V3::NewsletterSubscriberSerializer
11
+ prefixed_id_attributes :customer
11
12
  end
12
13
  end
13
14
  end
@@ -13,7 +13,7 @@ module Spree
13
13
  created_at: :string
14
14
 
15
15
  attribute :variant_id do |price_history|
16
- price_history.variant.prefixed_id
16
+ Spree::Variant.prefixed_id_for(price_history.variant_id)
17
17
  end
18
18
 
19
19
  attribute :price_id do |price_history|
@@ -34,13 +34,18 @@ module Spree
34
34
  attribute :currently_active, &:currently_active?
35
35
 
36
36
  # Cheap counts so the index can render "12 products / 36 prices"
37
- # without forcing each row to expand the children.
37
+ # without forcing each row to expand the children. The index counts
38
+ # the whole page at once; a single list asks for its own.
38
39
  attribute :products_count do |pl|
39
- pl.products.count
40
+ counts = params[:products_counts]
41
+
42
+ (counts || Spree::PriceList.products_count_by_list(pl.id)).fetch(pl.id, 0)
40
43
  end
41
44
 
42
45
  attribute :prices_count do |pl|
43
- pl.prices.count
46
+ counts = params[:prices_counts]
47
+
48
+ counts ? counts.fetch(pl.id, 0) : pl.prices.count
44
49
  end
45
50
 
46
51
  # Always expanded: a list's bands are the rest of the sentence its
@@ -17,7 +17,7 @@ module Spree
17
17
  attributes :min_quantity
18
18
 
19
19
  attribute :variant_id do |price|
20
- price&.variant&.prefixed_id
20
+ Spree::Variant.prefixed_id_for(price&.variant_id)
21
21
  end
22
22
 
23
23
  attributes created_at: :iso8601, updated_at: :iso8601
@@ -32,8 +32,8 @@ module Spree
32
32
  #
33
33
  # Read from a count the controller made for the whole page when there
34
34
  # is one; a single payout read falls back to asking for its own.
35
- attribute(:transfers_count) do |payout, params|
36
- counts = params && params[:transfer_counts]
35
+ attribute(:transfers_count) do |payout|
36
+ counts = params[:transfer_counts]
37
37
 
38
38
  counts ? counts.fetch(payout.id, 0) : payout.transfers.count
39
39
  end
@@ -54,9 +54,7 @@ module Spree
54
54
  # so the minimum-products requirement asks the same question free.
55
55
  attributes :products_count
56
56
 
57
- attribute :users_count do |seller|
58
- seller.users.size
59
- end
57
+ attributes :users_count
60
58
 
61
59
  # How far through the marketplace's checklist this seller is — what
62
60
  # the list's progress column and the profile's badge render. The
@@ -7,6 +7,7 @@ module Spree
7
7
 
8
8
  typelize customer_id: [:string, nullable: true],
9
9
  created_by_id: [:string, nullable: true],
10
+ created_by_type: [:string, nullable: true, enum: Spree::Actor::BUILT_IN_KINDS, enum_type_name: 'ActorKind'],
10
11
  memo: [:string, nullable: true],
11
12
  amount: [:string, nullable: false], amount_used: [:string, nullable: false],
12
13
  amount_remaining: [:string, nullable: false],
@@ -27,7 +28,8 @@ module Spree
27
28
  # money_attributes types its fields nullable for gated guests; the
28
29
  # Admin API never gates prices.
29
30
  typelize amount_authorized: [:string, nullable: false]
30
- prefixed_id_attributes :customer, :created_by
31
+ prefixed_id_attributes :customer
32
+ actor_attributes :created_by
31
33
 
32
34
  # Why the credit exists: the return, exchange, claim or gift card
33
35
  # that issued it, or null when an admin issued it by hand. The type
@@ -50,10 +52,6 @@ module Spree
50
52
  one :customer,
51
53
  resource: proc { Spree.api.admin_customer_serializer },
52
54
  if: proc { expand?('customer') }
53
-
54
- one :created_by,
55
- resource: proc { Spree.api.admin_admin_user_serializer },
56
- if: proc { expand?('created_by') }
57
55
  end
58
56
  end
59
57
  end
@@ -154,9 +154,14 @@ module Spree
154
154
 
155
155
  def self.preference_type_hint(schema, name)
156
156
  types = Array(schema['type'])
157
- hint = TYPELIZER_TYPES.fetch((types - ['null']).first, :unknown)
158
157
  options = {}
159
158
  options[:nullable] = true if types.include?('null')
159
+ if types.include?('array')
160
+ hint = TYPELIZER_TYPES.fetch(Array(schema.dig('items', 'type')).first, :unknown)
161
+ options[:multi] = true
162
+ else
163
+ hint = TYPELIZER_TYPES.fetch((types - ['null']).first, :unknown)
164
+ end
160
165
  if schema['enum']
161
166
  options[:enum] = schema['enum'].compact
162
167
  options[:enum_type_name] = PREFERENCE_LIST_NAMES[name.to_sym] if PREFERENCE_LIST_NAMES.key?(name.to_sym)
@@ -16,7 +16,11 @@ module Spree
16
16
  subscriber.verified_at&.iso8601
17
17
  end
18
18
 
19
- prefixed_id_attributes :customer
19
+ # Shown to the account's owner only: for anyone else, posting an email
20
+ # must not tell them whether an account exists for it.
21
+ attribute :customer_id do |subscriber|
22
+ subscriber.customer&.prefixed_id if current_user && subscriber.customer_id == current_user.id
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Api
5
+ module V3
6
+ module Seller
7
+ # A downloadable file on a seller's own product. Leaves out the
8
+ # provider's settings: they configure the operator's own systems, not
9
+ # the seller's listing.
10
+ class DigitalAssetSerializer < V3::DigitalAssetSerializer
11
+ include Spree::Api::V3::DigitalAssetDetails
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -22,8 +22,8 @@ module Spree
22
22
  # How many earnings this settlement covers. Read from a count the
23
23
  # controller made for the whole page when there is one; a single
24
24
  # payout read falls back to asking for its own.
25
- attribute(:transfers_count) do |payout, params|
26
- counts = params && params[:transfer_counts]
25
+ attribute(:transfers_count) do |payout|
26
+ counts = params[:transfer_counts]
27
27
 
28
28
  counts ? counts.fetch(payout.id, 0) : payout.transfers.count
29
29
  end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Api
3
+ module V3
4
+ module Seller
5
+ # What a seller may know about the marketplace they sell on: its name,
6
+ # and the settings that shape what the panel lets them do.
7
+ class StoreSerializer < V3::BaseSerializer
8
+ typelize name: :string
9
+
10
+ attributes :name
11
+
12
+ preference_attributes Spree::Store, :seller_digital_assets_enabled, :seller_digital_asset_content_types,
13
+ :review_seller_digital_asset_changes
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end