spree_api 5.6.0.rc1 → 5.6.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.
- checksums.yaml +4 -4
- data/app/controllers/spree/api/v3/store/carts_controller.rb +15 -2
- data/app/controllers/spree/api/v3/store/newsletter_subscribers_controller.rb +68 -1
- data/app/serializers/spree/api/v3/admin/customer_serializer.rb +10 -0
- data/app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb +15 -0
- data/app/serializers/spree/api/v3/customer_serializer.rb +7 -1
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +7 -2
- data/lib/spree/api/dependencies.rb +1 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0b165c94c2975472728b228761cc6cb41f1a677a6368dc9a0be992b5840444cd
|
|
4
|
+
data.tar.gz: 1ee381dfe91a8ccc58e959cec117f1beb992dde08b6c04488e18ea45baf1ec3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71038600173f2b56747b328f32fab9030ade806d07f3823e1e3e261098f85bdf24d54b0e8c7eb131e669a1f856bbb84c0d6cd3bba2974eef2739c87b73e27ddd
|
|
7
|
+
data.tar.gz: 8847edd08dbe74a437313d47add4fc557f5930dfdce57cca0b8812249e1f9ea40cc2416aef628e97a13a999777031bd6de381532a4093bceb6311f554854acb8
|
|
@@ -83,10 +83,15 @@ module Spree
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
# PATCH /api/v3/store/carts/:id/associate
|
|
86
|
-
# Associates a guest cart with the currently authenticated user
|
|
87
|
-
# Requires: JWT authentication + cart ID in URL
|
|
86
|
+
# Associates a guest cart with the currently authenticated user.
|
|
87
|
+
# Requires: JWT authentication + cart ID in URL + possession of the
|
|
88
|
+
# cart's token (x-spree-token). The token is the credential that
|
|
89
|
+
# authorizes claiming the cart, so it is required even when the cart
|
|
90
|
+
# is already the caller's own.
|
|
88
91
|
def associate
|
|
89
92
|
@cart = find_cart_for_association
|
|
93
|
+
authorize!(:update, @cart, cart_token)
|
|
94
|
+
require_cart_token!
|
|
90
95
|
|
|
91
96
|
result = Spree.cart_associate_service.call(guest_order: @cart, user: current_user, guest_only: true)
|
|
92
97
|
|
|
@@ -177,6 +182,14 @@ module Spree
|
|
|
177
182
|
def find_cart_for_association
|
|
178
183
|
current_store.carts.where(user: [nil, current_user]).find_by_prefix_id!(params[:id])
|
|
179
184
|
end
|
|
185
|
+
|
|
186
|
+
# Claiming a cart requires presenting its token, even when the caller
|
|
187
|
+
# already owns it — the token, not ownership, is the claim credential.
|
|
188
|
+
def require_cart_token!
|
|
189
|
+
valid = cart_token.present? && cart_token == @cart.token
|
|
190
|
+
|
|
191
|
+
raise CanCan::AccessDenied.new(nil, :update, @cart) unless valid
|
|
192
|
+
end
|
|
180
193
|
end
|
|
181
194
|
end
|
|
182
195
|
end
|
|
@@ -7,7 +7,7 @@ module Spree
|
|
|
7
7
|
rate_limit to: Spree::Api::Config[:rate_limit_register],
|
|
8
8
|
within: Spree::Api::Config[:rate_limit_window].seconds,
|
|
9
9
|
store: Rails.cache,
|
|
10
|
-
only: [:create, :verify],
|
|
10
|
+
only: [:create, :verify, :request_unsubscribe],
|
|
11
11
|
with: RATE_LIMIT_RESPONSE
|
|
12
12
|
|
|
13
13
|
# POST /api/v3/store/newsletter_subscribers
|
|
@@ -53,6 +53,57 @@ module Spree
|
|
|
53
53
|
render json: serialize_resource(subscriber)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# DELETE /api/v3/store/newsletter_subscribers/:id
|
|
57
|
+
# Accepts either of:
|
|
58
|
+
# - Authorization: Bearer <jwt> — logged-in customer destroying their own subscription
|
|
59
|
+
# - ?token=<unsubscribe-token> — bearer from `subscriber.generate_token_for(:unsubscribe)`,
|
|
60
|
+
# typically clicked from an unsubscribe email
|
|
61
|
+
def destroy
|
|
62
|
+
subscriber = find_owned_subscriber || find_subscriber_by_unsubscribe_token
|
|
63
|
+
|
|
64
|
+
if subscriber.blank?
|
|
65
|
+
return render_error(
|
|
66
|
+
code: ERROR_CODES[:invalid_token],
|
|
67
|
+
message: Spree.t(:newsletter_unsubscribe_token_invalid, scope: :api),
|
|
68
|
+
status: :unprocessable_content
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
linked_user = subscriber.user
|
|
73
|
+
subscriber.destroy!
|
|
74
|
+
|
|
75
|
+
# Keep accepts_email_marketing in sync, but only when no subscriptions remain.
|
|
76
|
+
if linked_user&.accepts_email_marketing? && Spree::NewsletterSubscriber.where(user_id: linked_user.id).none?
|
|
77
|
+
linked_user.update(accepts_email_marketing: false)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
head :no_content
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# POST /api/v3/store/newsletter_subscribers/request_unsubscribe
|
|
84
|
+
# Publishes a `newsletter_subscriber.unsubscribe_requested` event carrying the unsubscribe token.
|
|
85
|
+
# Always returns 202 to prevent email enumeration.
|
|
86
|
+
def request_unsubscribe
|
|
87
|
+
subscriber = Spree::NewsletterSubscriber.for_store(current_store).find_by(email: params[:email])
|
|
88
|
+
|
|
89
|
+
if subscriber
|
|
90
|
+
payload = {
|
|
91
|
+
id: subscriber.prefixed_id,
|
|
92
|
+
email: subscriber.email,
|
|
93
|
+
unsubscribe_token: subscriber.generate_token_for(:unsubscribe),
|
|
94
|
+
store_id: current_store.prefixed_id,
|
|
95
|
+
customer_id: subscriber.user&.prefixed_id
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
redirect_url = validated_redirect_url
|
|
99
|
+
payload[:redirect_url] = redirect_url if redirect_url.present?
|
|
100
|
+
|
|
101
|
+
subscriber.publish_event('newsletter_subscriber.unsubscribe_requested', payload)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
head :accepted
|
|
105
|
+
end
|
|
106
|
+
|
|
56
107
|
protected
|
|
57
108
|
|
|
58
109
|
def serializer_class
|
|
@@ -61,6 +112,22 @@ module Spree
|
|
|
61
112
|
|
|
62
113
|
private
|
|
63
114
|
|
|
115
|
+
def find_owned_subscriber
|
|
116
|
+
return unless current_user
|
|
117
|
+
|
|
118
|
+
Spree::NewsletterSubscriber.
|
|
119
|
+
for_store(current_store).
|
|
120
|
+
accessible_by(current_ability, :destroy).
|
|
121
|
+
find_by_prefix_id(params[:id])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def find_subscriber_by_unsubscribe_token
|
|
125
|
+
return if params[:token].blank?
|
|
126
|
+
|
|
127
|
+
subscriber = Spree::NewsletterSubscriber.for_store(current_store).find_by_token_for(:unsubscribe, params[:token])
|
|
128
|
+
subscriber if subscriber&.prefixed_id == params[:id]
|
|
129
|
+
end
|
|
130
|
+
|
|
64
131
|
# Drop redirect_url when it isn't in the store's allow-list — secure-by-default,
|
|
65
132
|
# mirrors password_resets. Returning nil omits it from the webhook payload rather
|
|
66
133
|
# than rejecting the request, so callers can't probe the allow-list via 4xx errors.
|
|
@@ -85,6 +85,16 @@ module Spree
|
|
|
85
85
|
one :bill_address, key: :default_billing_address, resource: proc { Spree.api.admin_address_serializer }, if: proc { expand?('default_billing_address') }
|
|
86
86
|
one :ship_address, key: :default_shipping_address, resource: proc { Spree.api.admin_address_serializer }, if: proc { expand?('default_shipping_address') }
|
|
87
87
|
|
|
88
|
+
# Override the inherited always-on store association to gate it behind
|
|
89
|
+
# expand? — the admin customers index serializes many rows and an
|
|
90
|
+
# always-on association would fire one query per row.
|
|
91
|
+
one :newsletter_subscriber,
|
|
92
|
+
resource: proc { Spree.api.admin_newsletter_subscriber_serializer },
|
|
93
|
+
if: proc { expand?('newsletter_subscriber') } do |user, params|
|
|
94
|
+
store = params&.dig(:store) || Spree::Current.store
|
|
95
|
+
user.newsletter_subscriber(store)
|
|
96
|
+
end
|
|
97
|
+
|
|
88
98
|
many :orders,
|
|
89
99
|
resource: proc { Spree.api.admin_order_serializer },
|
|
90
100
|
if: proc { expand?('orders') }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module Api
|
|
5
|
+
module V3
|
|
6
|
+
module Admin
|
|
7
|
+
# Admin API Newsletter Subscriber Serializer
|
|
8
|
+
# Back-office surface, nested under the admin customer serializer. Extends the
|
|
9
|
+
# customer-facing serializer so public fields stay in sync.
|
|
10
|
+
class NewsletterSubscriberSerializer < V3::NewsletterSubscriberSerializer
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -8,7 +8,8 @@ module Spree
|
|
|
8
8
|
full_name: :string,
|
|
9
9
|
phone: [:string, nullable: true], accepts_email_marketing: :boolean,
|
|
10
10
|
available_store_credit_total: :string, display_available_store_credit_total: :string,
|
|
11
|
-
default_billing_address: { nullable: true }, default_shipping_address: { nullable: true }
|
|
11
|
+
default_billing_address: { nullable: true }, default_shipping_address: { nullable: true },
|
|
12
|
+
newsletter_subscriber: { nullable: true }
|
|
12
13
|
|
|
13
14
|
attributes :email, :first_name, :last_name, :phone, :accepts_email_marketing
|
|
14
15
|
|
|
@@ -31,6 +32,11 @@ module Spree
|
|
|
31
32
|
many :addresses, resource: proc { Spree.api.address_serializer }
|
|
32
33
|
one :bill_address, key: :default_billing_address, resource: proc { Spree.api.address_serializer }
|
|
33
34
|
one :ship_address, key: :default_shipping_address, resource: proc { Spree.api.address_serializer }
|
|
35
|
+
|
|
36
|
+
one :newsletter_subscriber, resource: proc { Spree.api.newsletter_subscriber_serializer } do |user, params|
|
|
37
|
+
store = params&.dig(:store) || Spree::Current.store
|
|
38
|
+
user.newsletter_subscriber(store)
|
|
39
|
+
end
|
|
34
40
|
end
|
|
35
41
|
end
|
|
36
42
|
end
|
data/config/locales/en.yml
CHANGED
|
@@ -13,6 +13,7 @@ en:
|
|
|
13
13
|
guest_checkout_not_allowed: You must be signed in to complete checkout.
|
|
14
14
|
storefront_login_required: Authentication required to access this store.
|
|
15
15
|
negative_quantity: quantity is negative
|
|
16
|
+
newsletter_unsubscribe_token_invalid: Newsletter unsubscribe token is invalid or has expired.
|
|
16
17
|
newsletter_verification_token_invalid: Newsletter verification token is invalid or has expired.
|
|
17
18
|
order:
|
|
18
19
|
could_not_transition: The order could not be transitioned. Please fix the errors and try again.
|
data/config/routes.rb
CHANGED
|
@@ -59,10 +59,15 @@ Spree::Core::Engine.add_routes do
|
|
|
59
59
|
# Customers
|
|
60
60
|
resources :customers, only: [:create]
|
|
61
61
|
|
|
62
|
-
# Newsletter Subscriptions
|
|
63
|
-
|
|
62
|
+
# Newsletter Subscriptions
|
|
63
|
+
# - create + verify + request_unsubscribe are guest-accessible
|
|
64
|
+
# - destroy accepts either an unsubscribe token (from email links) or JWT auth
|
|
65
|
+
# A signed-in customer reads their own subscription (and its id) off the
|
|
66
|
+
# `newsletter_subscriber` association on GET /customers/me.
|
|
67
|
+
resources :newsletter_subscribers, only: [:create, :destroy] do
|
|
64
68
|
collection do
|
|
65
69
|
post :verify
|
|
70
|
+
post :request_unsubscribe
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
73
|
|
|
@@ -172,6 +172,7 @@ module Spree
|
|
|
172
172
|
admin_state_serializer: 'Spree::Api::V3::Admin::StateSerializer',
|
|
173
173
|
admin_discount_serializer: 'Spree::Api::V3::Admin::DiscountSerializer',
|
|
174
174
|
admin_customer_serializer: 'Spree::Api::V3::Admin::CustomerSerializer',
|
|
175
|
+
admin_newsletter_subscriber_serializer: 'Spree::Api::V3::Admin::NewsletterSubscriberSerializer',
|
|
175
176
|
admin_order_serializer: 'Spree::Api::V3::Admin::OrderSerializer',
|
|
176
177
|
admin_product_serializer: 'Spree::Api::V3::Admin::ProductSerializer',
|
|
177
178
|
admin_variant_serializer: 'Spree::Api::V3::Admin::VariantSerializer',
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.6.0.
|
|
4
|
+
version: 5.6.0.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rswag-specs
|
|
@@ -72,14 +72,14 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - '='
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 5.6.0.
|
|
75
|
+
version: 5.6.0.rc2
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - '='
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 5.6.0.
|
|
82
|
+
version: 5.6.0.rc2
|
|
83
83
|
description: Spree's API
|
|
84
84
|
email:
|
|
85
85
|
- hello@spreecommerce.org
|
|
@@ -248,6 +248,7 @@ files:
|
|
|
248
248
|
- app/serializers/spree/api/v3/admin/line_item_serializer.rb
|
|
249
249
|
- app/serializers/spree/api/v3/admin/market_serializer.rb
|
|
250
250
|
- app/serializers/spree/api/v3/admin/media_serializer.rb
|
|
251
|
+
- app/serializers/spree/api/v3/admin/newsletter_subscriber_serializer.rb
|
|
251
252
|
- app/serializers/spree/api/v3/admin/option_type_serializer.rb
|
|
252
253
|
- app/serializers/spree/api/v3/admin/option_value_serializer.rb
|
|
253
254
|
- app/serializers/spree/api/v3/admin/order_serializer.rb
|
|
@@ -379,9 +380,9 @@ licenses:
|
|
|
379
380
|
- BSD-3-Clause
|
|
380
381
|
metadata:
|
|
381
382
|
bug_tracker_uri: https://github.com/spree/spree/issues
|
|
382
|
-
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.
|
|
383
|
+
changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc2
|
|
383
384
|
documentation_uri: https://docs.spreecommerce.org/
|
|
384
|
-
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.
|
|
385
|
+
source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc2
|
|
385
386
|
post_install_message:
|
|
386
387
|
rdoc_options: []
|
|
387
388
|
require_paths:
|