spree_api 4.2.3 → 4.3.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +14 -1
  3. data/app/controllers/concerns/spree/api/v2/product_list_includes.rb +23 -0
  4. data/app/controllers/spree/api/v1/classifications_controller.rb +4 -3
  5. data/app/controllers/spree/api/v1/orders_controller.rb +5 -1
  6. data/app/controllers/spree/api/v1/products_controller.rb +2 -2
  7. data/app/controllers/spree/api/v1/taxonomies_controller.rb +1 -1
  8. data/app/controllers/spree/api/v2/base_controller.rb +29 -6
  9. data/app/controllers/spree/api/v2/platform/addresses_controller.rb +19 -0
  10. data/app/controllers/spree/api/v2/platform/classifications_controller.rb +43 -0
  11. data/app/controllers/spree/api/v2/platform/cms_pages_controller.rb +15 -0
  12. data/app/controllers/spree/api/v2/platform/cms_sections_controller.rb +34 -0
  13. data/app/controllers/spree/api/v2/platform/countries_controller.rb +19 -0
  14. data/app/controllers/spree/api/v2/platform/menu_items_controller.rb +35 -0
  15. data/app/controllers/spree/api/v2/platform/menus_controller.rb +19 -0
  16. data/app/controllers/spree/api/v2/platform/option_types_controller.rb +15 -0
  17. data/app/controllers/spree/api/v2/platform/option_values_controller.rb +19 -0
  18. data/app/controllers/spree/api/v2/platform/products_controller.rb +21 -0
  19. data/app/controllers/spree/api/v2/platform/resource_controller.rb +112 -0
  20. data/app/controllers/spree/api/v2/platform/taxons_controller.rb +30 -0
  21. data/app/controllers/spree/api/v2/platform/users_controller.rb +28 -0
  22. data/app/controllers/spree/api/v2/resource_controller.rb +18 -6
  23. data/app/controllers/spree/api/v2/storefront/account/addresses_controller.rb +2 -14
  24. data/app/controllers/spree/api/v2/storefront/account/credit_cards_controller.rb +13 -11
  25. data/app/controllers/spree/api/v2/storefront/account/orders_controller.rb +2 -2
  26. data/app/controllers/spree/api/v2/storefront/account_controller.rb +32 -1
  27. data/app/controllers/spree/api/v2/storefront/cart_controller.rb +28 -6
  28. data/app/controllers/spree/api/v2/storefront/checkout_controller.rb +2 -1
  29. data/app/controllers/spree/api/v2/storefront/cms_pages_controller.rb +41 -0
  30. data/app/controllers/spree/api/v2/storefront/countries_controller.rb +9 -14
  31. data/app/controllers/spree/api/v2/storefront/menus_controller.rb +35 -0
  32. data/app/controllers/spree/api/v2/storefront/order_status_controller.rb +1 -1
  33. data/app/controllers/spree/api/v2/storefront/products_controller.rb +6 -12
  34. data/app/controllers/spree/api/v2/storefront/stores_controller.rb +1 -1
  35. data/app/controllers/spree/api/v2/storefront/taxons_controller.rb +7 -8
  36. data/app/helpers/spree/api/v2/display_money_helper.rb +2 -2
  37. data/app/models/spree/api_configuration.rb +1 -0
  38. data/app/models/spree/api_dependencies.rb +19 -5
  39. data/app/serializers/concerns/spree/api/v2/resource_serializer_concern.rb +16 -0
  40. data/app/serializers/spree/api/v2/base_serializer.rb +32 -0
  41. data/app/serializers/spree/api/v2/platform/address_serializer.rb +15 -0
  42. data/app/serializers/spree/api/v2/platform/base_serializer.rb +10 -0
  43. data/app/serializers/spree/api/v2/platform/classification_serializer.rb +14 -0
  44. data/app/serializers/spree/api/v2/platform/cms_page_serializer.rb +13 -0
  45. data/app/serializers/spree/api/v2/platform/cms_section_serializer.rb +11 -0
  46. data/app/serializers/spree/api/v2/platform/country_serializer.rb +13 -0
  47. data/app/serializers/spree/api/v2/platform/image_serializer.rb +15 -0
  48. data/app/serializers/spree/api/v2/platform/menu_item_serializer.rb +22 -0
  49. data/app/serializers/spree/api/v2/platform/menu_serializer.rb +13 -0
  50. data/app/serializers/spree/api/v2/platform/option_type_serializer.rb +13 -0
  51. data/app/serializers/spree/api/v2/platform/option_value_serializer.rb +13 -0
  52. data/app/serializers/spree/api/v2/platform/product_property_serializer.rb +11 -0
  53. data/app/serializers/spree/api/v2/platform/product_serializer.rb +78 -0
  54. data/app/serializers/spree/api/v2/platform/state_serializer.rb +13 -0
  55. data/app/serializers/spree/api/v2/platform/stock_item_serializer.rb +20 -0
  56. data/app/serializers/spree/api/v2/platform/stock_location_serializer.rb +16 -0
  57. data/app/serializers/spree/api/v2/platform/store_serializer.rb +14 -0
  58. data/app/serializers/spree/api/v2/platform/tax_category_serializer.rb +13 -0
  59. data/app/serializers/spree/api/v2/platform/taxon_image_serializer.rb +13 -0
  60. data/app/serializers/spree/api/v2/platform/taxon_serializer.rb +38 -0
  61. data/app/serializers/spree/api/v2/platform/taxonomy_serializer.rb +14 -0
  62. data/app/serializers/spree/api/v2/platform/user_serializer.rb +21 -0
  63. data/app/serializers/spree/api/v2/platform/variant_serializer.rb +57 -0
  64. data/app/serializers/spree/v2/storefront/base_serializer.rb +3 -2
  65. data/app/serializers/spree/v2/storefront/cms_page_serializer.rb +14 -0
  66. data/app/serializers/spree/v2/storefront/cms_section_serializer.rb +28 -0
  67. data/app/serializers/spree/v2/storefront/country_serializer.rb +4 -4
  68. data/app/serializers/spree/v2/storefront/estimated_shipping_rate_serializer.rb +2 -0
  69. data/app/serializers/spree/v2/storefront/menu_item_serializer.rb +37 -0
  70. data/app/serializers/spree/v2/storefront/menu_serializer.rb +13 -0
  71. data/app/serializers/spree/v2/storefront/product_property_serializer.rb +1 -1
  72. data/app/serializers/spree/v2/storefront/product_serializer.rb +4 -1
  73. data/app/serializers/spree/v2/storefront/store_serializer.rb +10 -0
  74. data/app/serializers/spree/v2/storefront/taxon_serializer.rb +6 -5
  75. data/config/initializers/doorkeeper.rb +8 -1
  76. data/config/initializers/rabl.rb +9 -0
  77. data/config/routes.rb +115 -3
  78. data/db/migrate/20210727102516_change_integer_id_columns_type.rb +9 -0
  79. data/docs/oauth/index.yml +2 -2
  80. data/docs/v2/platform/index.yaml +1501 -0
  81. data/docs/v2/storefront/index.yaml +613 -433
  82. data/lib/spree/api.rb +1 -0
  83. data/lib/spree/api/engine.rb +6 -17
  84. data/lib/spree/api/testing_support/v2/base.rb +1 -1
  85. data/lib/spree/api/testing_support/v2/platform_contexts.rb +214 -0
  86. data/lib/spree/api/testing_support/v2/serializers_params.rb +14 -0
  87. data/spree_api.gemspec +5 -2
  88. metadata +107 -11
  89. data/app/assets/javascripts/spree/api/main.js +0 -36
  90. data/app/assets/javascripts/spree/api/storefront/cart.js +0 -49
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class CmsPagesController < ::Spree::Api::V2::ResourceController
6
+ private
7
+
8
+ def model_class
9
+ Spree::CmsPage
10
+ end
11
+
12
+ def resource
13
+ @resource ||= scope.find_by(slug: params[:id]) || scope.find(params[:id])
14
+ end
15
+
16
+ def resource_serializer
17
+ Spree::Api::Dependencies.storefront_cms_page_serializer.constantize
18
+ end
19
+
20
+ def collection_serializer
21
+ Spree::Api::Dependencies.storefront_cms_page_serializer.constantize
22
+ end
23
+
24
+ def collection_finder
25
+ Spree::Api::Dependencies.storefront_cms_page_finder.constantize
26
+ end
27
+
28
+ def scope
29
+ super.by_locale(I18n.locale)
30
+ end
31
+
32
+ def scope_includes
33
+ {
34
+ cms_sections: :linked_resource
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -6,27 +6,22 @@ module Spree
6
6
  private
7
7
 
8
8
  def serialize_collection(collection)
9
- collection_serializer.new(collection).serializable_hash
9
+ collection_serializer.new(
10
+ collection,
11
+ collection_options(collection).merge(params: collection_serializer_params)
12
+ ).serializable_hash
10
13
  end
11
14
 
12
- def serialize_resource(resource)
13
- resource_serializer.new(
14
- resource,
15
- include: resource_includes,
16
- fields: sparse_fields,
17
- params: resource_serializer_params
18
- ).serializable_hash
15
+ def serializer_params
16
+ super.merge(include_states: true)
19
17
  end
20
18
 
21
- def resource_serializer_params
22
- {
23
- include_states: true,
24
- current_store: current_store
25
- }
19
+ def collection_serializer_params
20
+ serializer_params.merge(include_states: false)
26
21
  end
27
22
 
28
23
  def resource
29
- return scope.default if params[:iso] == 'default'
24
+ return current_store.default_country if params[:iso] == 'default'
30
25
 
31
26
  scope.find_by(iso: params[:iso]&.upcase) ||
32
27
  scope.find_by(id: params[:iso]&.upcase) ||
@@ -0,0 +1,35 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Storefront
5
+ class MenusController < ::Spree::Api::V2::ResourceController
6
+ private
7
+
8
+ def resource_serializer
9
+ Spree::Api::Dependencies.storefront_menu_serializer.constantize
10
+ end
11
+
12
+ def collection_serializer
13
+ Spree::Api::Dependencies.storefront_menu_serializer.constantize
14
+ end
15
+
16
+ def collection_finder
17
+ Spree::Api::Dependencies.storefront_menu_finder.constantize
18
+ end
19
+
20
+ def model_class
21
+ Spree::Menu
22
+ end
23
+
24
+ def scope
25
+ super.by_locale(I18n.locale)
26
+ end
27
+
28
+ def scope_includes
29
+ { menu_items: [:children, :parent, :icon] }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,7 +10,7 @@ module Spree
10
10
  private
11
11
 
12
12
  def resource
13
- resource = resource_finder.new(number: params[:number], token: order_token).execute.take
13
+ resource = resource_finder.new(number: params[:number], token: order_token, store: current_store).execute.take
14
14
  raise ActiveRecord::RecordNotFound if resource.nil?
15
15
 
16
16
  resource
@@ -3,18 +3,20 @@ module Spree
3
3
  module V2
4
4
  module Storefront
5
5
  class ProductsController < ::Spree::Api::V2::ResourceController
6
- private
6
+ include ::Spree::Api::V2::ProductListIncludes
7
+
8
+ protected
7
9
 
8
10
  def sorted_collection
9
11
  collection_sorter.new(collection, current_currency, params, allowed_sort_attributes).call
10
12
  end
11
13
 
12
14
  def collection
13
- collection_finder.new(scope: scope, params: params, current_currency: current_currency).execute
15
+ @collection ||= collection_finder.new(scope: scope, params: finder_params).execute
14
16
  end
15
17
 
16
18
  def resource
17
- scope.find_by(slug: params[:id]) || scope.find(params[:id])
19
+ @resource ||= scope.find_by(slug: params[:id]) || scope.find(params[:id])
18
20
  end
19
21
 
20
22
  def collection_sorter
@@ -38,15 +40,7 @@ module Spree
38
40
  end
39
41
 
40
42
  def scope_includes
41
- {
42
- master: :default_price,
43
- variants: [],
44
- variant_images: [],
45
- taxons: [],
46
- product_properties: :property,
47
- option_types: :option_values,
48
- variants_including_master: %i[default_price option_values]
49
- }
43
+ product_list_includes
50
44
  end
51
45
  end
52
46
  end
@@ -10,7 +10,7 @@ module Spree
10
10
  end
11
11
 
12
12
  def resource
13
- scope.find_by!(code: params[:code])
13
+ @resource ||= scope.find_by!(code: params[:code])
14
14
  end
15
15
 
16
16
  def resource_serializer
@@ -18,15 +18,11 @@ module Spree
18
18
  end
19
19
 
20
20
  def paginated_collection
21
- collection_paginator.new(collection, params).call
22
- end
23
-
24
- def collection
25
- collection_finder.new(scope: scope, params: params).execute
21
+ @paginated_collection ||= collection_paginator.new(collection, params).call
26
22
  end
27
23
 
28
24
  def resource
29
- scope.find_by(permalink: params[:id]) || scope.find(params[:id])
25
+ @resource ||= scope.find_by(permalink: params[:id]) || scope.find(params[:id])
30
26
  end
31
27
 
32
28
  def model_class
@@ -34,16 +30,19 @@ module Spree
34
30
  end
35
31
 
36
32
  def scope_includes
37
- node_includes = %i[icon products parent taxonomy]
33
+ node_includes = %i[icon parent taxonomy]
38
34
 
39
35
  {
40
36
  parent: node_includes,
41
37
  children: node_includes,
42
38
  taxonomy: [root: node_includes],
43
- products: [],
44
39
  icon: [attachment_attachment: :blob]
45
40
  }
46
41
  end
42
+
43
+ def serializer_params
44
+ super.merge(include_products: action_name == 'show')
45
+ end
47
46
  end
48
47
  end
49
48
  end
@@ -13,7 +13,7 @@ module Spree
13
13
  price = find_price(product_or_variant, currency)
14
14
  return nil if price.new_record?
15
15
 
16
- format('%.2f', price.amount)
16
+ price.amount
17
17
  end
18
18
 
19
19
  def display_price(product_or_variant, currency)
@@ -27,7 +27,7 @@ module Spree
27
27
  price = find_price(product_or_variant, currency)
28
28
  return nil if price.new_record? || price.compare_at_amount.blank?
29
29
 
30
- format('%.2f', price.compare_at_amount)
30
+ price.compare_at_amount
31
31
  end
32
32
 
33
33
  def display_compare_at_price(product_or_variant, currency)
@@ -1,6 +1,7 @@
1
1
  module Spree
2
2
  class ApiConfiguration < Preferences::Configuration
3
3
  preference :requires_authentication, :boolean, default: true
4
+ preference :api_v2_cache_ttl, :integer, default: 3600 # 1 hour in seconds
4
5
  preference :api_v2_content_type, :string, default: 'application/vnd.api+json'
5
6
  preference :api_v2_per_page_limit, :integer, default: 100
6
7
  end
@@ -5,18 +5,21 @@ module Spree
5
5
  INJECTION_POINTS = [
6
6
  :storefront_cart_create_service, :storefront_cart_add_item_service, :storefront_cart_remove_line_item_service,
7
7
  :storefront_cart_remove_item_service, :storefront_cart_set_item_quantity_service, :storefront_cart_recalculate_service,
8
+ :storefront_cms_page_serializer, :storefront_cms_page_finder,
8
9
  :storefront_cart_update, :storefront_coupon_handler, :storefront_checkout_next_service, :storefront_checkout_advance_service,
9
10
  :storefront_checkout_update_service, :storefront_checkout_complete_service, :storefront_checkout_add_store_credit_service,
10
11
  :storefront_checkout_remove_store_credit_service, :storefront_checkout_get_shipping_rates_service,
11
12
  :storefront_cart_compare_line_items_service, :storefront_cart_serializer, :storefront_credit_card_serializer,
12
13
  :storefront_credit_card_finder, :storefront_shipment_serializer, :storefront_payment_method_serializer, :storefront_country_finder,
13
- :storefront_country_serializer, :storefront_current_order_finder, :storefront_completed_order_finder, :storefront_order_sorter,
14
- :storefront_collection_paginator, :storefront_user_serializer, :storefront_products_sorter, :storefront_products_finder,
15
- :storefront_product_serializer, :storefront_taxon_serializer, :storefront_taxon_finder, :storefront_find_by_variant_finder,
16
- :storefront_cart_update_service, :storefront_cart_estimate_shipping_rates_service, :storefront_estimated_shipment_serializer,
14
+ :storefront_country_serializer, :storefront_menu_serializer, :storefront_menu_finder, :storefront_current_order_finder,
15
+ :storefront_completed_order_finder, :storefront_order_sorter, :storefront_collection_paginator, :storefront_user_serializer,
16
+ :storefront_products_sorter, :storefront_products_finder, :storefront_product_serializer, :storefront_taxon_serializer,
17
+ :storefront_taxon_finder, :storefront_find_by_variant_finder, :storefront_cart_update_service,
18
+ :storefront_cart_estimate_shipping_rates_service, :storefront_estimated_shipment_serializer,
17
19
  :storefront_store_serializer, :storefront_address_serializer, :storefront_order_serializer,
18
20
  :storefront_account_create_address_service, :storefront_account_update_address_service, :storefront_address_finder,
19
- :storefront_collection_sorter, :error_handler
21
+ :storefront_account_create_service, :storefront_account_update_service, :storefront_collection_sorter, :error_handler,
22
+ :storefront_cart_empty_service, :storefront_cart_destroy_service, :storefront_credit_cards_destroy_service
20
23
  ].freeze
21
24
 
22
25
  attr_accessor *INJECTION_POINTS
@@ -38,6 +41,8 @@ module Spree
38
41
  @storefront_cart_set_item_quantity_service = Spree::Dependencies.cart_set_item_quantity_service
39
42
  @storefront_cart_recalculate_service = Spree::Dependencies.cart_recalculate_service
40
43
  @storefront_cart_estimate_shipping_rates_service = Spree::Dependencies.cart_estimate_shipping_rates_service
44
+ @storefront_cart_empty_service = Spree::Dependencies.cart_empty_service
45
+ @storefront_cart_destroy_service = Spree::Dependencies.cart_destroy_service
41
46
 
42
47
  # coupon code handler
43
48
  @storefront_coupon_handler = Spree::Dependencies.coupon_handler
@@ -52,14 +57,21 @@ module Spree
52
57
  @storefront_checkout_get_shipping_rates_service = Spree::Dependencies.checkout_get_shipping_rates_service
53
58
 
54
59
  # account services
60
+ @storefront_account_create_service = Spree::Dependencies.account_create_service
61
+ @storefront_account_update_service = Spree::Dependencies.account_update_service
55
62
  @storefront_account_create_address_service = Spree::Dependencies.account_create_address_service
56
63
  @storefront_account_update_address_service = Spree::Dependencies.account_update_address_service
57
64
 
65
+ # credit cards
66
+ @storefront_credit_cards_destroy_service = Spree::Dependencies.credit_cards_destroy_service
67
+
58
68
  # serializers
59
69
  @storefront_address_serializer = 'Spree::V2::Storefront::AddressSerializer'
60
70
  @storefront_cart_serializer = 'Spree::V2::Storefront::CartSerializer'
71
+ @storefront_cms_page_serializer = 'Spree::V2::Storefront::CmsPageSerializer'
61
72
  @storefront_credit_card_serializer = 'Spree::V2::Storefront::CreditCardSerializer'
62
73
  @storefront_country_serializer = 'Spree::V2::Storefront::CountrySerializer'
74
+ @storefront_menu_serializer = 'Spree::V2::Storefront::MenuSerializer'
63
75
  @storefront_user_serializer = 'Spree::V2::Storefront::UserSerializer'
64
76
  @storefront_shipment_serializer = 'Spree::V2::Storefront::ShipmentSerializer'
65
77
  @storefront_taxon_serializer = 'Spree::V2::Storefront::TaxonSerializer'
@@ -80,6 +92,8 @@ module Spree
80
92
  # finders
81
93
  @storefront_address_finder = Spree::Dependencies.address_finder
82
94
  @storefront_country_finder = Spree::Dependencies.country_finder
95
+ @storefront_cms_page_finder = Spree::Dependencies.cms_page_finder
96
+ @storefront_menu_finder = Spree::Dependencies.menu_finder
83
97
  @storefront_current_order_finder = Spree::Dependencies.current_order_finder
84
98
  @storefront_completed_order_finder = Spree::Dependencies.completed_order_finder
85
99
  @storefront_credit_card_finder = Spree::Dependencies.credit_card_finder
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module ResourceSerializerConcern
5
+ extend ActiveSupport::Concern
6
+
7
+ def self.included(base)
8
+ model_klazz = "Spree::#{base.to_s.demodulize.gsub(/Serializer/, '')}".constantize
9
+
10
+ base.set_type model_klazz.json_api_type
11
+ base.attributes(*model_klazz.json_api_columns)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ class BaseSerializer
5
+ include JSONAPI::Serializer
6
+
7
+ # to learn more about caching, please refer to:
8
+ # https://github.com/jsonapi-serializer/jsonapi-serializer#caching
9
+ # https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching
10
+ cache_options(store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: Spree::Api::Config[:api_v2_cache_ttl])
11
+
12
+ def self.record_cache_options(options, fieldset, include_list, params)
13
+ opts = options.dup
14
+
15
+ params_cache_key = params.map do |param|
16
+ next if param.nil? || param.last.nil?
17
+
18
+ if param.last.respond_to?(:cache_key_with_version)
19
+ param.last.cache_key_with_version
20
+ else
21
+ param.last.to_s.downcase
22
+ end
23
+ end.compact.reject(&:blank?).join('-')
24
+
25
+ opts[:namespace] += "-#{params_cache_key}"
26
+
27
+ super(opts, fieldset, include_list, params)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class AddressSerializer < BaseSerializer
6
+ include ::Spree::Api::V2::ResourceSerializerConcern
7
+
8
+ belongs_to :country
9
+ belongs_to :state
10
+ belongs_to :user
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class BaseSerializer < ::Spree::Api::V2::BaseSerializer
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class ClassificationSerializer < BaseSerializer
6
+ include ::Spree::Api::V2::ResourceSerializerConcern
7
+
8
+ belongs_to :product
9
+ belongs_to :taxon
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class CmsPageSerializer < BaseSerializer
6
+ include ::Spree::Api::V2::ResourceSerializerConcern
7
+
8
+ has_many :cms_sections, serializer: :cms_section
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class CmsSectionSerializer < BaseSerializer
6
+ include ::Spree::Api::V2::ResourceSerializerConcern
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module Api
3
+ module V2
4
+ module Platform
5
+ class CountrySerializer < BaseSerializer
6
+ include ::Spree::Api::V2::ResourceSerializerConcern
7
+
8
+ has_many :states
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end