spree_api 1.3.1 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +23 -18
- data/app/controllers/spree/api/base_controller.rb +1 -1
- data/app/controllers/spree/api/checkouts_controller.rb +90 -0
- data/app/controllers/spree/api/inventory_units_controller.rb +48 -0
- data/app/controllers/spree/api/orders_controller.rb +0 -17
- data/app/controllers/spree/api/product_properties_controller.rb +3 -2
- data/app/controllers/spree/api/return_authorizations_controller.rb +3 -2
- data/app/controllers/spree/api/taxonomies_controller.rb +3 -2
- data/app/controllers/spree/api/users_controller.rb +51 -0
- data/app/helpers/spree/api/api_helpers.rb +4 -0
- data/app/models/spree/order_decorator.rb +4 -2
- data/app/views/spree/api/images/show.v1.rabl +1 -0
- data/app/views/spree/api/{v1/inventory_units → inventory_units}/show.rabl +0 -0
- data/app/views/spree/api/users/index.v1.rabl +7 -0
- data/app/views/spree/api/users/new.v1.rabl +3 -0
- data/app/views/spree/api/users/show.v1.rabl +3 -0
- data/config/routes.rb +2 -0
- data/lib/spree/api/responders/rabl_template.rb +1 -1
- data/spec/controllers/spree/api/checkouts_controller_spec.rb +102 -0
- data/spec/controllers/spree/api/{v1/inventory_units_controller_spec.rb → inventory_units_controller_spec.rb} +1 -2
- data/spec/controllers/spree/api/orders_controller_spec.rb +1 -74
- data/spec/controllers/spree/api/product_properties_controller_spec.rb +2 -2
- data/spec/controllers/spree/api/users_controller_spec.rb +126 -0
- data/spec/models/spree/order_spec.rb +1 -1
- metadata +19 -11
- data/app/controllers/spree/api/v1/base_controller.rb +0 -111
- data/app/controllers/spree/api/v1/inventory_units_controller.rb +0 -50
data/LICENSE
CHANGED
@@ -1,22 +1,27 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2007-2013, Spree Commerce, Inc. and other contributors
|
2
|
+
All rights reserved.
|
2
3
|
|
3
|
-
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Spree nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
15
27
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -53,7 +53,7 @@ module Spree
|
|
53
53
|
|
54
54
|
def authenticate_user
|
55
55
|
if requires_authentication? || api_key.present?
|
56
|
-
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key)
|
56
|
+
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
57
57
|
render "spree/api/errors/invalid_api_key", :status => 401 and return
|
58
58
|
end
|
59
59
|
else
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
class CheckoutsController < Spree::Api::BaseController
|
4
|
+
before_filter :load_order, :only => :update
|
5
|
+
before_filter :associate_user, :only => :update
|
6
|
+
|
7
|
+
include Spree::Core::ControllerHelpers::Auth
|
8
|
+
include Spree::Core::ControllerHelpers::Order
|
9
|
+
|
10
|
+
respond_to :json
|
11
|
+
|
12
|
+
def create
|
13
|
+
@order = Order.build_from_api(current_api_user, nested_params)
|
14
|
+
next!(:status => 201)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
if @order.update_attributes(object_params)
|
19
|
+
state_callback(:after) if @order.next
|
20
|
+
respond_with(@order, :default_template => 'spree/api/orders/show')
|
21
|
+
else
|
22
|
+
respond_with(@order, :default_template => 'spree/api/orders/could_not_transition', :status => 422)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def object_params
|
29
|
+
# For payment step, filter order parameters to produce the expected nested attributes for a single payment and its source, discarding attributes for payment methods other than the one selected
|
30
|
+
if @order.payment?
|
31
|
+
if params[:payment_source].present? && source_params = params.delete(:payment_source)[params[:order][:payments_attributes].first[:payment_method_id].underscore]
|
32
|
+
params[:order][:payments_attributes].first[:source_attributes] = source_params
|
33
|
+
end
|
34
|
+
if params[:order].present? && params[:order][:payments_attributes]
|
35
|
+
params[:order][:payments_attributes].first[:amount] = @order.total
|
36
|
+
end
|
37
|
+
end
|
38
|
+
params[:order]
|
39
|
+
end
|
40
|
+
|
41
|
+
def nested_params
|
42
|
+
map_nested_attributes_keys Order, params[:order] || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Should be overriden if you have areas of your checkout that don't match
|
46
|
+
# up to a step within checkout_steps, such as a registration step
|
47
|
+
def skip_state_validation?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_order
|
52
|
+
@order = Spree::Order.find_by_number!(params[:id])
|
53
|
+
raise_insufficient_quantity and return if @order.insufficient_stock_lines.present?
|
54
|
+
@order.state = params[:state] if params[:state]
|
55
|
+
state_callback(:before)
|
56
|
+
end
|
57
|
+
|
58
|
+
def raise_insufficient_quantity
|
59
|
+
respond_with(@order, :default_template => 'spree/api/orders/insufficient_quantity')
|
60
|
+
end
|
61
|
+
|
62
|
+
def state_callback(before_or_after = :before)
|
63
|
+
method_name = :"#{before_or_after}_#{@order.state}"
|
64
|
+
send(method_name) if respond_to?(method_name, true)
|
65
|
+
end
|
66
|
+
|
67
|
+
def before_address
|
68
|
+
@order.bill_address ||= Address.default
|
69
|
+
@order.ship_address ||= Address.default
|
70
|
+
end
|
71
|
+
|
72
|
+
def before_delivery
|
73
|
+
return if params[:order].present?
|
74
|
+
@order.shipping_method ||= (@order.rate_hash.first && @order.rate_hash.first[:shipping_method])
|
75
|
+
end
|
76
|
+
|
77
|
+
def before_payment
|
78
|
+
@order.payments.destroy_all if request.put?
|
79
|
+
end
|
80
|
+
|
81
|
+
def next!(options={})
|
82
|
+
if @order.valid? && @order.next
|
83
|
+
render 'spree/api/orders/show', :status => options[:status] || 200
|
84
|
+
else
|
85
|
+
render 'spree/api/orders/could_not_transition', :status => 422
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
class InventoryUnitsController < Spree::Api::BaseController
|
4
|
+
before_filter :prepare_event, :only => :update
|
5
|
+
|
6
|
+
def show
|
7
|
+
@inventory_unit = inventory_unit
|
8
|
+
end
|
9
|
+
|
10
|
+
def update
|
11
|
+
authorize! :update, Order
|
12
|
+
|
13
|
+
inventory_unit.transaction do
|
14
|
+
if inventory_unit.update_attributes(params[:inventory_unit])
|
15
|
+
fire
|
16
|
+
render :show, :status => 200
|
17
|
+
else
|
18
|
+
invalid_resource!(inventory_unit)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def inventory_unit
|
26
|
+
@inventory_unit ||= InventoryUnit.find(params[:id])
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepare_event
|
30
|
+
return unless @event = params[:fire]
|
31
|
+
|
32
|
+
can_event = "can_#{@event}?"
|
33
|
+
|
34
|
+
unless inventory_unit.respond_to?(can_event) &&
|
35
|
+
inventory_unit.send(can_event)
|
36
|
+
render :text => { :exception => "cannot transition to #{@event}" }.to_json,
|
37
|
+
:status => 200
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fire
|
43
|
+
inventory_unit.send("#{@event}!") if @event
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -31,23 +31,6 @@ module Spree
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def address
|
35
|
-
order.build_ship_address(params[:shipping_address]) if params[:shipping_address]
|
36
|
-
order.build_bill_address(params[:billing_address]) if params[:billing_address]
|
37
|
-
next!
|
38
|
-
end
|
39
|
-
|
40
|
-
def delivery
|
41
|
-
begin
|
42
|
-
ShippingMethod.find(params[:shipping_method_id])
|
43
|
-
rescue ActiveRecord::RecordNotFound
|
44
|
-
render :invalid_shipping_method, :status => 422
|
45
|
-
else
|
46
|
-
order.update_attribute(:shipping_method_id, params[:shipping_method_id])
|
47
|
-
next!
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
34
|
def cancel
|
52
35
|
order.cancel!
|
53
36
|
render :show
|
@@ -7,8 +7,9 @@ module Spree
|
|
7
7
|
before_filter :product_property, :only => [:show, :update, :destroy]
|
8
8
|
|
9
9
|
def index
|
10
|
-
@product_properties = @product.product_properties.
|
11
|
-
|
10
|
+
@product_properties = @product.product_properties.
|
11
|
+
ransack(params[:q]).result.
|
12
|
+
page(params[:page]).per(params[:per_page])
|
12
13
|
respond_with(@product_properties)
|
13
14
|
end
|
14
15
|
|
@@ -6,8 +6,9 @@ module Spree
|
|
6
6
|
before_filter :authorize_admin!
|
7
7
|
|
8
8
|
def index
|
9
|
-
@return_authorizations = order.return_authorizations.
|
10
|
-
|
9
|
+
@return_authorizations = order.return_authorizations.
|
10
|
+
ransack(params[:q]).result.
|
11
|
+
page(params[:page]).per(params[:per_page])
|
11
12
|
respond_with(@return_authorizations)
|
12
13
|
end
|
13
14
|
|
@@ -4,8 +4,9 @@ module Spree
|
|
4
4
|
respond_to :json
|
5
5
|
|
6
6
|
def index
|
7
|
-
@taxonomies = Taxonomy.order('name').includes(:root => :children).
|
8
|
-
|
7
|
+
@taxonomies = Taxonomy.order('name').includes(:root => :children).
|
8
|
+
ransack(params[:q]).result.
|
9
|
+
page(params[:page]).per(params[:per_page])
|
9
10
|
respond_with(@taxonomies)
|
10
11
|
end
|
11
12
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Spree
|
2
|
+
module Api
|
3
|
+
class UsersController < Spree::Api::BaseController
|
4
|
+
respond_to :json
|
5
|
+
|
6
|
+
def index
|
7
|
+
@users = Spree.user_class.accessible_by(current_ability,:read).ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
|
8
|
+
respond_with(@users)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
authorize! :show, user
|
13
|
+
respond_with(user)
|
14
|
+
end
|
15
|
+
|
16
|
+
def new
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
authorize! :create, Spree.user_class
|
21
|
+
@user = Spree.user_class.new(params[:user])
|
22
|
+
if @user.save
|
23
|
+
respond_with(@user, :status => 201, :default_template => :show)
|
24
|
+
else
|
25
|
+
invalid_resource!(@user)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
authorize! :update, user
|
31
|
+
if user.update_attributes(params[:user])
|
32
|
+
respond_with(user, :status => 200, :default_template => :show)
|
33
|
+
else
|
34
|
+
invalid_resource!(user)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy
|
39
|
+
authorize! :destroy, user
|
40
|
+
user.destroy
|
41
|
+
respond_with(user, :status => 204)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def user
|
47
|
+
@user ||= Spree.user_class.find(params[:id])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -2,8 +2,10 @@ Spree::Order.class_eval do
|
|
2
2
|
def self.build_from_api(user, params)
|
3
3
|
order = create
|
4
4
|
params[:line_items_attributes] ||= []
|
5
|
-
params[:line_items_attributes].
|
6
|
-
|
5
|
+
unless params[:line_items_attributes].empty?
|
6
|
+
params[:line_items_attributes].each_key do |k|
|
7
|
+
order.add_variant(Spree::Variant.find(params[:line_items_attributes][k][:variant_id]), params[:line_items_attributes][k][:quantity])
|
8
|
+
end
|
7
9
|
end
|
8
10
|
|
9
11
|
order.user = user
|
File without changes
|
data/config/routes.rb
CHANGED
@@ -15,6 +15,7 @@ Spree::Core::Engine.routes.prepend do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
resources :images
|
18
|
+
resources :checkouts
|
18
19
|
resources :variants, :only => [:index] do
|
19
20
|
end
|
20
21
|
|
@@ -53,5 +54,6 @@ Spree::Core::Engine.routes.prepend do
|
|
53
54
|
resources :taxons
|
54
55
|
end
|
55
56
|
resources :inventory_units, :only => [:show, :update]
|
57
|
+
resources :users
|
56
58
|
end
|
57
59
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
describe Api::CheckoutsController do
|
5
|
+
render_views
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
stub_authentication!
|
9
|
+
Spree::Config[:track_inventory_levels] = false
|
10
|
+
country_zone = create(:zone, :name => 'CountryZone')
|
11
|
+
@state = create(:state)
|
12
|
+
@country = @state.country
|
13
|
+
country_zone.members.create(:zoneable => @country)
|
14
|
+
|
15
|
+
@shipping_method = create(:shipping_method, :zone => country_zone)
|
16
|
+
@payment_method = create(:payment_method)
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
Spree::Config[:track_inventory_levels] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
context "POST 'create'" do
|
24
|
+
it "creates a new order when no parameters are passed" do
|
25
|
+
api_post :create
|
26
|
+
|
27
|
+
json_response['number'].should be_present
|
28
|
+
response.status.should == 201
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "PUT 'update'" do
|
33
|
+
let(:order) { create(:order) }
|
34
|
+
|
35
|
+
before(:each) do
|
36
|
+
Order.any_instance.stub(:confirmation_required? => true)
|
37
|
+
Order.any_instance.stub(:payment_required? => true)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "will return an error if the order cannot transition" do
|
41
|
+
order.update_column(:state, "address")
|
42
|
+
api_put :update, :id => order.to_param
|
43
|
+
json_response['error'].should =~ /could not be transitioned/
|
44
|
+
response.status.should == 422
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can update addresses and transition from address to delivery" do
|
48
|
+
order.update_column(:state, "address")
|
49
|
+
shipping_address = billing_address = {
|
50
|
+
:firstname => 'John',
|
51
|
+
:lastname => 'Doe',
|
52
|
+
:address1 => '7735 Old Georgetown Road',
|
53
|
+
:city => 'Bethesda',
|
54
|
+
:phone => '3014445002',
|
55
|
+
:zipcode => '20814',
|
56
|
+
:state_id => @state.id,
|
57
|
+
:country_id => @country.id
|
58
|
+
}
|
59
|
+
api_put :update,
|
60
|
+
:id => order.to_param,
|
61
|
+
:order => { :bill_address_attributes => billing_address, :ship_address_attributes => shipping_address }
|
62
|
+
|
63
|
+
json_response['state'].should == 'delivery'
|
64
|
+
json_response['bill_address']['firstname'].should == 'John'
|
65
|
+
json_response['ship_address']['firstname'].should == 'John'
|
66
|
+
response.status.should == 200
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can update shipping method and transition from delivery to payment" do
|
70
|
+
order.update_column(:state, "delivery")
|
71
|
+
api_put :update, :id => order.to_param, :order => { :shipping_method_id => @shipping_method.id }
|
72
|
+
|
73
|
+
json_response['shipments'][0]['shipping_method']['name'].should == @shipping_method.name
|
74
|
+
json_response['state'].should == 'payment'
|
75
|
+
response.status.should == 200
|
76
|
+
end
|
77
|
+
|
78
|
+
it "can update payment method and transition from payment to confirm" do
|
79
|
+
order.update_column(:state, "payment")
|
80
|
+
api_put :update, :id => order.to_param, :order => { :payments_attributes => [{ :payment_method_id => @payment_method.id }] }
|
81
|
+
json_response['state'].should == 'confirm'
|
82
|
+
json_response['payments'][0]['payment_method']['name'].should == @payment_method.name
|
83
|
+
response.status.should == 200
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can transition from confirm to complete" do
|
87
|
+
order.update_column(:state, "confirm")
|
88
|
+
Spree::Order.any_instance.stub(:payment_required? => false)
|
89
|
+
api_put :update, :id => order.to_param
|
90
|
+
json_response['state'].should == 'complete'
|
91
|
+
response.status.should == 200
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns the order if the order is already complete" do
|
95
|
+
order.update_column(:state, "complete")
|
96
|
+
api_put :update, :id => order.to_param
|
97
|
+
json_response['number'].should == order.number
|
98
|
+
response.status.should == 200
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module Spree
|
4
|
-
describe Api::
|
4
|
+
describe Api::InventoryUnitsController do
|
5
5
|
render_views
|
6
6
|
|
7
7
|
before do
|
@@ -41,6 +41,5 @@ module Spree
|
|
41
41
|
json_response['exception'].should match /cannot transition to bad/
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
45
44
|
end
|
46
45
|
end
|
@@ -53,14 +53,9 @@ module Spree
|
|
53
53
|
assert_unauthorized!
|
54
54
|
end
|
55
55
|
|
56
|
-
it "cannot change delivery information on an order that doesn't belong to them" do
|
57
|
-
api_put :delivery, :id => order.to_param
|
58
|
-
assert_unauthorized!
|
59
|
-
end
|
60
|
-
|
61
56
|
it "can create an order" do
|
62
57
|
variant = create(:variant)
|
63
|
-
api_post :create, :order => { :line_items =>
|
58
|
+
api_post :create, :order => { :line_items => { "0" => { :variant_id => variant.to_param, :quantity => 5 } } }
|
64
59
|
response.status.should == 201
|
65
60
|
order = Order.last
|
66
61
|
order.line_items.count.should == 1
|
@@ -97,52 +92,6 @@ module Spree
|
|
97
92
|
let!(:shipping_method) { create(:shipping_method) }
|
98
93
|
let!(:payment_method) { create(:payment_method) }
|
99
94
|
|
100
|
-
it "can add address information to an order" do
|
101
|
-
api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
|
102
|
-
|
103
|
-
response.status.should == 200
|
104
|
-
order.reload
|
105
|
-
order.shipping_address.reload
|
106
|
-
order.billing_address.reload
|
107
|
-
# We can assume the rest of the parameters are set if these two are
|
108
|
-
order.shipping_address.firstname.should == shipping_address[:firstname]
|
109
|
-
order.billing_address.firstname.should == billing_address[:firstname]
|
110
|
-
order.state.should == "delivery"
|
111
|
-
json_response["shipping_methods"].should_not be_empty
|
112
|
-
end
|
113
|
-
|
114
|
-
it "can add just shipping address information to an order" do
|
115
|
-
api_put :address, :id => order.to_param, :shipping_address => shipping_address
|
116
|
-
response.status.should == 200
|
117
|
-
order.reload
|
118
|
-
order.shipping_address.reload
|
119
|
-
order.shipping_address.firstname.should == shipping_address[:firstname]
|
120
|
-
order.bill_address.should be_nil
|
121
|
-
end
|
122
|
-
|
123
|
-
it "cannot use an address that has no valid shipping methods" do
|
124
|
-
shipping_method.destroy
|
125
|
-
api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
|
126
|
-
response.status.should == 422
|
127
|
-
json_response["errors"]["base"].should == ["No shipping methods available for selected location, please change your address and try again."]
|
128
|
-
end
|
129
|
-
|
130
|
-
it "can not add invalid ship address information to an order" do
|
131
|
-
shipping_address[:firstname] = ""
|
132
|
-
api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
|
133
|
-
|
134
|
-
response.status.should == 422
|
135
|
-
json_response["errors"]["ship_address.firstname"].should_not be_blank
|
136
|
-
end
|
137
|
-
|
138
|
-
it "can not add invalid ship address information to an order" do
|
139
|
-
billing_address[:firstname] = ""
|
140
|
-
api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
|
141
|
-
|
142
|
-
response.status.should == 422
|
143
|
-
json_response["errors"]["bill_address.firstname"].should_not be_blank
|
144
|
-
end
|
145
|
-
|
146
95
|
it "can add line items" do
|
147
96
|
api_put :update, :id => order.to_param, :order => { :line_items => [{:variant_id => create(:variant).id, :quantity => 2}] }
|
148
97
|
|
@@ -155,28 +104,6 @@ module Spree
|
|
155
104
|
order.line_items << create(:line_item)
|
156
105
|
end
|
157
106
|
|
158
|
-
context "for delivery" do
|
159
|
-
before do
|
160
|
-
order.update_attribute(:state, "delivery")
|
161
|
-
end
|
162
|
-
|
163
|
-
it "can select a shipping method for an order" do
|
164
|
-
order.shipping_method.should be_nil
|
165
|
-
api_put :delivery, :id => order.to_param, :shipping_method_id => shipping_method.id
|
166
|
-
response.status.should == 200
|
167
|
-
order.reload
|
168
|
-
order.state.should == "payment"
|
169
|
-
order.shipping_method.should == shipping_method
|
170
|
-
end
|
171
|
-
|
172
|
-
it "cannot select an invalid shipping method for an order" do
|
173
|
-
order.shipping_method.should be_nil
|
174
|
-
api_put :delivery, :id => order.to_param, :shipping_method_id => '1234567890'
|
175
|
-
response.status.should == 422
|
176
|
-
json_response["errors"].should include("Invalid shipping method specified.")
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
107
|
it "can empty an order" do
|
181
108
|
api_put :empty, :id => order.to_param
|
182
109
|
response.status.should == 200
|
@@ -6,8 +6,8 @@ module Spree
|
|
6
6
|
render_views
|
7
7
|
|
8
8
|
let!(:product) { create(:product) }
|
9
|
-
let!(:property_1) {product.product_properties.create(:property_name => "My Property 1", :value => "my value 1")}
|
10
|
-
let!(:property_2) {product.product_properties.create(:property_name => "My Property 2", :value => "my value 2")}
|
9
|
+
let!(:property_1) {product.product_properties.create(:property_name => "My Property 1", :value => "my value 1", :position => 0)}
|
10
|
+
let!(:property_2) {product.product_properties.create(:property_name => "My Property 2", :value => "my value 2", :position => 1)}
|
11
11
|
|
12
12
|
let(:attributes) { [:id, :product_id, :property_id, :value, :property_name] }
|
13
13
|
let(:resource_scoping) { { :product_id => product.to_param } }
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
describe Api::UsersController do
|
5
|
+
render_views
|
6
|
+
|
7
|
+
let(:user) { create(:user) }
|
8
|
+
let(:stranger) { create(:user, :email => 'stranger@example.com') }
|
9
|
+
let(:attributes) { [:id, :email, :created_at, :updated_at] }
|
10
|
+
|
11
|
+
before { stub_authentication! }
|
12
|
+
|
13
|
+
context "as a normal user" do
|
14
|
+
before { Spree::LegacyUser.stub :find_by_spree_api_key => user }
|
15
|
+
|
16
|
+
it "can get own details" do
|
17
|
+
api_get :show, :id => user.id
|
18
|
+
|
19
|
+
json_response['email'].should eq user.email
|
20
|
+
end
|
21
|
+
|
22
|
+
it "cannot get other users details" do
|
23
|
+
api_get :show, :id => stranger.id
|
24
|
+
|
25
|
+
assert_unauthorized!
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can learn how to create a new user" do
|
29
|
+
api_get :new
|
30
|
+
json_response["attributes"].should == attributes.map(&:to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can create a new user" do
|
34
|
+
api_post :create, :user => { :email => 'new@example.com', :password => 'spree123', :password_confirmation => 'spree123' }
|
35
|
+
json_response['email'].should eq 'new@example.com'
|
36
|
+
end
|
37
|
+
|
38
|
+
# there's no validations on LegacyUser?
|
39
|
+
xit "cannot create a new user with invalid attributes" do
|
40
|
+
api_post :create, :user => {}
|
41
|
+
response.status.should == 422
|
42
|
+
json_response["error"].should == "Invalid resource. Please fix errors and try again."
|
43
|
+
errors = json_response["errors"]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can update own details" do
|
47
|
+
api_put :update, :id => user.id, :user => { :email => "mine@example.com" }
|
48
|
+
json_response['email'].should eq 'mine@example.com'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "cannot update other users details" do
|
52
|
+
api_put :update, :id => stranger.id, :user => { :email => "mine@example.com" }
|
53
|
+
assert_unauthorized!
|
54
|
+
end
|
55
|
+
|
56
|
+
it "can delete itself" do
|
57
|
+
api_delete :destroy, :id => user.id
|
58
|
+
response.status.should == 204
|
59
|
+
end
|
60
|
+
|
61
|
+
it "cannot delete other user" do
|
62
|
+
api_delete :destroy, :id => stranger.id
|
63
|
+
assert_unauthorized!
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should only get own details on index" do
|
67
|
+
2.times { create(:user) }
|
68
|
+
api_get :index
|
69
|
+
|
70
|
+
Spree.user_class.count.should eq 3
|
71
|
+
json_response['count'].should eq 1
|
72
|
+
json_response['users'].size.should eq 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "as an admin" do
|
77
|
+
sign_in_as_admin!
|
78
|
+
|
79
|
+
it "gets all users" do
|
80
|
+
Spree::LegacyUser.stub :find_by_spree_api_key => current_api_user
|
81
|
+
|
82
|
+
2.times { create(:user) }
|
83
|
+
|
84
|
+
api_get :index
|
85
|
+
Spree.user_class.count.should eq 2
|
86
|
+
json_response['count'].should eq 2
|
87
|
+
json_response['users'].size.should eq 2
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'can control the page size through a parameter' do
|
91
|
+
2.times { create(:user) }
|
92
|
+
api_get :index, :per_page => 1
|
93
|
+
json_response['count'].should == 1
|
94
|
+
json_response['current_page'].should == 1
|
95
|
+
json_response['pages'].should == 2
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'can query the results through a paramter' do
|
99
|
+
expected_result = create(:user, :email => 'brian@spreecommerce.com')
|
100
|
+
api_get :index, :q => { :email_cont => 'brian' }
|
101
|
+
json_response['count'].should == 1
|
102
|
+
json_response['users'].first['email'].should eq expected_result.email
|
103
|
+
end
|
104
|
+
|
105
|
+
it "can create" do
|
106
|
+
api_post :create, :user => { :email => "new@example.com", :password => 'spree123', :password_confirmation => 'spree123' }
|
107
|
+
json_response.should have_attributes(attributes)
|
108
|
+
response.status.should == 201
|
109
|
+
end
|
110
|
+
|
111
|
+
it "can destroy user without orders" do
|
112
|
+
user.orders.destroy_all
|
113
|
+
api_delete :destroy, :id => user.id
|
114
|
+
response.status.should == 204
|
115
|
+
end
|
116
|
+
|
117
|
+
it "cannot destroy user with orders" do
|
118
|
+
create(:completed_order_with_totals, :user => user)
|
119
|
+
api_delete :destroy, :id => user.id
|
120
|
+
json_response["exception"].should eq "Spree::LegacyUser::DestroyWithOrdersError"
|
121
|
+
response.status.should == 422
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -7,7 +7,7 @@ module Spree
|
|
7
7
|
it 'can build an order from API parameters' do
|
8
8
|
product = Spree::Product.create!(:name => 'Test', :sku => 'TEST-1', :price => 33.22)
|
9
9
|
variant_id = product.master.id
|
10
|
-
order = Order.build_from_api(user, { :line_items_attributes =>
|
10
|
+
order = Order.build_from_api(user, { :line_items_attributes => { "0" => { :variant_id => variant_id, :quantity => 5 }}})
|
11
11
|
|
12
12
|
order.user.should == user
|
13
13
|
line_item = order.line_items.first
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spree_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: spree_core
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - '='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.3.
|
21
|
+
version: 1.3.2
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - '='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.3.
|
29
|
+
version: 1.3.2
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: versioncake
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,8 +89,10 @@ files:
|
|
89
89
|
- Rakefile
|
90
90
|
- app/controllers/spree/api/addresses_controller.rb
|
91
91
|
- app/controllers/spree/api/base_controller.rb
|
92
|
+
- app/controllers/spree/api/checkouts_controller.rb
|
92
93
|
- app/controllers/spree/api/countries_controller.rb
|
93
94
|
- app/controllers/spree/api/images_controller.rb
|
95
|
+
- app/controllers/spree/api/inventory_units_controller.rb
|
94
96
|
- app/controllers/spree/api/line_items_controller.rb
|
95
97
|
- app/controllers/spree/api/orders_controller.rb
|
96
98
|
- app/controllers/spree/api/payments_controller.rb
|
@@ -100,8 +102,7 @@ files:
|
|
100
102
|
- app/controllers/spree/api/shipments_controller.rb
|
101
103
|
- app/controllers/spree/api/taxonomies_controller.rb
|
102
104
|
- app/controllers/spree/api/taxons_controller.rb
|
103
|
-
- app/controllers/spree/api/
|
104
|
-
- app/controllers/spree/api/v1/inventory_units_controller.rb
|
105
|
+
- app/controllers/spree/api/users_controller.rb
|
105
106
|
- app/controllers/spree/api/variants_controller.rb
|
106
107
|
- app/controllers/spree/api/zones_controller.rb
|
107
108
|
- app/helpers/spree/api/api_helpers.rb
|
@@ -122,6 +123,7 @@ files:
|
|
122
123
|
- app/views/spree/api/errors/not_found.v1.rabl
|
123
124
|
- app/views/spree/api/errors/unauthorized.v1.rabl
|
124
125
|
- app/views/spree/api/images/show.v1.rabl
|
126
|
+
- app/views/spree/api/inventory_units/show.rabl
|
125
127
|
- app/views/spree/api/line_items/new.v1.rabl
|
126
128
|
- app/views/spree/api/line_items/show.v1.rabl
|
127
129
|
- app/views/spree/api/orders/address.v1.rabl
|
@@ -158,7 +160,9 @@ files:
|
|
158
160
|
- app/views/spree/api/taxons/new.v1.rabl
|
159
161
|
- app/views/spree/api/taxons/show.v1.rabl
|
160
162
|
- app/views/spree/api/taxons/taxons.v1.rabl
|
161
|
-
- app/views/spree/api/
|
163
|
+
- app/views/spree/api/users/index.v1.rabl
|
164
|
+
- app/views/spree/api/users/new.v1.rabl
|
165
|
+
- app/views/spree/api/users/show.v1.rabl
|
162
166
|
- app/views/spree/api/variants/index.v1.rabl
|
163
167
|
- app/views/spree/api/variants/new.v1.rabl
|
164
168
|
- app/views/spree/api/variants/show.v1.rabl
|
@@ -183,8 +187,10 @@ files:
|
|
183
187
|
- script/rails
|
184
188
|
- spec/controllers/spree/api/addresses_controller_spec.rb
|
185
189
|
- spec/controllers/spree/api/base_controller_spec.rb
|
190
|
+
- spec/controllers/spree/api/checkouts_controller_spec.rb
|
186
191
|
- spec/controllers/spree/api/countries_controller_spec.rb
|
187
192
|
- spec/controllers/spree/api/images_controller_spec.rb
|
193
|
+
- spec/controllers/spree/api/inventory_units_controller_spec.rb
|
188
194
|
- spec/controllers/spree/api/line_items_controller_spec.rb
|
189
195
|
- spec/controllers/spree/api/orders_controller_spec.rb
|
190
196
|
- spec/controllers/spree/api/payments_controller_spec.rb
|
@@ -195,7 +201,7 @@ files:
|
|
195
201
|
- spec/controllers/spree/api/taxonomies_controller_spec.rb
|
196
202
|
- spec/controllers/spree/api/taxons_controller_spec.rb
|
197
203
|
- spec/controllers/spree/api/unauthenticated_products_controller_spec.rb
|
198
|
-
- spec/controllers/spree/api/
|
204
|
+
- spec/controllers/spree/api/users_controller_spec.rb
|
199
205
|
- spec/controllers/spree/api/variants_controller_spec.rb
|
200
206
|
- spec/controllers/spree/api/zones_controller_spec.rb
|
201
207
|
- spec/fixtures/thinking-cat.jpg
|
@@ -221,7 +227,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
221
227
|
version: '0'
|
222
228
|
segments:
|
223
229
|
- 0
|
224
|
-
hash:
|
230
|
+
hash: 2361962546786859221
|
225
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
226
232
|
none: false
|
227
233
|
requirements:
|
@@ -230,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
236
|
version: '0'
|
231
237
|
segments:
|
232
238
|
- 0
|
233
|
-
hash:
|
239
|
+
hash: 2361962546786859221
|
234
240
|
requirements: []
|
235
241
|
rubyforge_project:
|
236
242
|
rubygems_version: 1.8.23
|
@@ -240,8 +246,10 @@ summary: Spree's API
|
|
240
246
|
test_files:
|
241
247
|
- spec/controllers/spree/api/addresses_controller_spec.rb
|
242
248
|
- spec/controllers/spree/api/base_controller_spec.rb
|
249
|
+
- spec/controllers/spree/api/checkouts_controller_spec.rb
|
243
250
|
- spec/controllers/spree/api/countries_controller_spec.rb
|
244
251
|
- spec/controllers/spree/api/images_controller_spec.rb
|
252
|
+
- spec/controllers/spree/api/inventory_units_controller_spec.rb
|
245
253
|
- spec/controllers/spree/api/line_items_controller_spec.rb
|
246
254
|
- spec/controllers/spree/api/orders_controller_spec.rb
|
247
255
|
- spec/controllers/spree/api/payments_controller_spec.rb
|
@@ -252,7 +260,7 @@ test_files:
|
|
252
260
|
- spec/controllers/spree/api/taxonomies_controller_spec.rb
|
253
261
|
- spec/controllers/spree/api/taxons_controller_spec.rb
|
254
262
|
- spec/controllers/spree/api/unauthenticated_products_controller_spec.rb
|
255
|
-
- spec/controllers/spree/api/
|
263
|
+
- spec/controllers/spree/api/users_controller_spec.rb
|
256
264
|
- spec/controllers/spree/api/variants_controller_spec.rb
|
257
265
|
- spec/controllers/spree/api/zones_controller_spec.rb
|
258
266
|
- spec/fixtures/thinking-cat.jpg
|
@@ -1,111 +0,0 @@
|
|
1
|
-
module Spree
|
2
|
-
module Api
|
3
|
-
module V1
|
4
|
-
class BaseController < ActionController::Metal
|
5
|
-
include Spree::Api::ControllerSetup
|
6
|
-
|
7
|
-
attr_accessor :current_api_user
|
8
|
-
|
9
|
-
before_filter :set_content_type
|
10
|
-
before_filter :check_for_api_key, :if => :requires_authentication?
|
11
|
-
before_filter :authenticate_user
|
12
|
-
|
13
|
-
rescue_from Exception, :with => :error_during_processing
|
14
|
-
rescue_from CanCan::AccessDenied, :with => :unauthorized
|
15
|
-
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
|
16
|
-
|
17
|
-
helper Spree::Api::ApiHelpers
|
18
|
-
|
19
|
-
def map_nested_attributes_keys(klass, attributes)
|
20
|
-
nested_keys = klass.nested_attributes_options.keys
|
21
|
-
attributes.inject({}) do |h, (k,v)|
|
22
|
-
key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
|
23
|
-
h[key] = v
|
24
|
-
h
|
25
|
-
end.with_indifferent_access
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def set_content_type
|
31
|
-
content_type = case params[:format]
|
32
|
-
when "json"
|
33
|
-
"application/json"
|
34
|
-
when "xml"
|
35
|
-
"text/xml"
|
36
|
-
end
|
37
|
-
headers["Content-Type"] = content_type
|
38
|
-
end
|
39
|
-
|
40
|
-
def check_for_api_key
|
41
|
-
render "spree/api/v1/errors/must_specify_api_key", :status => 401 and return if api_key.blank?
|
42
|
-
end
|
43
|
-
|
44
|
-
def authenticate_user
|
45
|
-
if requires_authentication? || api_key.present?
|
46
|
-
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key)
|
47
|
-
render "spree/api/v1/errors/invalid_api_key", :status => 401 and return
|
48
|
-
end
|
49
|
-
else
|
50
|
-
# Effectively, an anonymous user
|
51
|
-
@current_api_user = Spree.user_class.new
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def unauthorized
|
56
|
-
render "spree/api/v1/errors/unauthorized", :status => 401 and return
|
57
|
-
end
|
58
|
-
|
59
|
-
def requires_authentication?
|
60
|
-
Spree::Api::Config[:requires_authentication]
|
61
|
-
end
|
62
|
-
|
63
|
-
def not_found
|
64
|
-
render "spree/api/v1/errors/not_found", :status => 404 and return
|
65
|
-
end
|
66
|
-
|
67
|
-
def error_during_processing(exception)
|
68
|
-
render :text => { exception: exception.message }.to_json,
|
69
|
-
:status => 422 and return
|
70
|
-
end
|
71
|
-
|
72
|
-
def current_ability
|
73
|
-
Spree::Ability.new(current_api_user)
|
74
|
-
end
|
75
|
-
|
76
|
-
def invalid_resource!(resource)
|
77
|
-
@resource = resource
|
78
|
-
render "spree/api/v1/errors/invalid_resource", :status => 422
|
79
|
-
end
|
80
|
-
|
81
|
-
def api_key
|
82
|
-
request.headers["X-Spree-Token"] || params[:token]
|
83
|
-
end
|
84
|
-
helper_method :api_key
|
85
|
-
|
86
|
-
def find_product(id)
|
87
|
-
begin
|
88
|
-
product_scope.find_by_permalink!(id.to_s)
|
89
|
-
rescue ActiveRecord::RecordNotFound
|
90
|
-
product_scope.find(id)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def product_scope
|
95
|
-
if current_api_user.has_spree_role?("admin")
|
96
|
-
scope = Product
|
97
|
-
unless params[:show_deleted]
|
98
|
-
scope = scope.not_deleted
|
99
|
-
end
|
100
|
-
else
|
101
|
-
scope = Product.active
|
102
|
-
end
|
103
|
-
|
104
|
-
scope.includes(:master)
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
@@ -1,50 +0,0 @@
|
|
1
|
-
module Spree
|
2
|
-
module Api
|
3
|
-
module V1
|
4
|
-
class InventoryUnitsController < Spree::Api::V1::BaseController
|
5
|
-
before_filter :prepare_event, :only => :update
|
6
|
-
|
7
|
-
def show
|
8
|
-
@inventory_unit = inventory_unit
|
9
|
-
end
|
10
|
-
|
11
|
-
def update
|
12
|
-
authorize! :update, Order
|
13
|
-
|
14
|
-
inventory_unit.transaction do
|
15
|
-
if inventory_unit.update_attributes(params[:inventory_unit])
|
16
|
-
fire
|
17
|
-
render :show, :status => 200
|
18
|
-
else
|
19
|
-
invalid_resource!(inventory_unit)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def inventory_unit
|
27
|
-
@inventory_unit ||= InventoryUnit.find(params[:id])
|
28
|
-
end
|
29
|
-
|
30
|
-
def prepare_event
|
31
|
-
return unless @event = params[:fire]
|
32
|
-
|
33
|
-
can_event = "can_#{@event}?"
|
34
|
-
|
35
|
-
unless inventory_unit.respond_to?(can_event) &&
|
36
|
-
inventory_unit.send(can_event)
|
37
|
-
render :text => { exception: "cannot transition to #{@event}" }.to_json,
|
38
|
-
:status => 200
|
39
|
-
false
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def fire
|
44
|
-
inventory_unit.send("#{@event}!") if @event
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|