├── docs
├── .nojekyll
├── source
│ ├── CNAME
│ ├── fonts
│ │ ├── slate.eot
│ │ ├── slate.ttf
│ │ ├── slate.woff
│ │ ├── slate.woff2
│ │ └── slate.svg
│ ├── images
│ │ ├── navbar.png
│ │ └── favicon.ico
│ ├── javascripts
│ │ ├── all_nosearch.js
│ │ ├── all.js
│ │ ├── app
│ │ │ ├── _toc.js
│ │ │ ├── _search.js
│ │ │ └── _lang.js
│ │ └── lib
│ │ │ ├── _jquery.highlight.js
│ │ │ ├── _energize.js
│ │ │ └── _imagesloaded.min.js
│ ├── includes
│ │ ├── _pagination.md
│ │ ├── _filtering.md
│ │ ├── _errors.md
│ │ ├── _states.md
│ │ ├── _countries.md
│ │ ├── _line_items.md
│ │ ├── _prices.md
│ │ ├── _orders.md
│ │ ├── _option_values.md
│ │ ├── _images.md
│ │ ├── _option_types.md
│ │ ├── _taxonomies.md
│ │ └── _taxons.md
│ ├── stylesheets
│ │ ├── _syntax.scss.erb
│ │ ├── _icon-font.scss
│ │ ├── print.css.scss
│ │ └── _variables.scss
│ ├── index.md
│ └── layouts
│ │ └── layout.erb
├── Rakefile
├── README.md
├── Dockerfile
├── Gemfile
├── config.rb
└── font-selection.json
├── .rspec
├── app
├── models
│ └── spree
│ │ ├── order_decorator.rb
│ │ ├── price_decorator.rb
│ │ ├── image_decorator.rb
│ │ ├── line_item_decorator.rb
│ │ ├── state_decorator.rb
│ │ └── base_decorator.rb
├── serializers
│ └── spree
│ │ ├── role_serializer.rb
│ │ ├── user_serializer.rb
│ │ ├── state_serializer.rb
│ │ ├── taxonomy_serializer.rb
│ │ ├── country_serializer.rb
│ │ ├── price_serializer.rb
│ │ ├── option_type_serializer.rb
│ │ ├── option_value_serializer.rb
│ │ ├── store_serializer.rb
│ │ ├── image_serializer.rb
│ │ ├── address_serializer.rb
│ │ ├── product_serializer.rb
│ │ ├── variant_serializer.rb
│ │ ├── base_serializer.rb
│ │ ├── line_item_serializer.rb
│ │ ├── taxon_serializer.rb
│ │ ├── order_serializer.rb
│ │ └── error_serializer.rb
└── controllers
│ ├── spree
│ └── api
│ │ └── v2
│ │ ├── base_controller.rb
│ │ ├── orders_controller.rb
│ │ ├── countries_controller.rb
│ │ ├── taxonomies_controller.rb
│ │ ├── prices_controller.rb
│ │ ├── states_controller.rb
│ │ ├── children_controller.rb
│ │ ├── images_controller.rb
│ │ ├── option_values_controller.rb
│ │ ├── option_types_controller.rb
│ │ ├── taxons_controller.rb
│ │ ├── variants_controller.rb
│ │ ├── line_items_controller.rb
│ │ └── products_controller.rb
│ └── concerns
│ └── spree
│ └── api
│ └── v2
│ └── renderable.rb
├── spec
├── models
│ └── spree
│ │ ├── price_decorator_spec.rb
│ │ └── base_decorator_spec.rb
├── support
│ └── shoulda_matchers.rb
├── lib
│ └── solidus_json_api
│ │ └── config_spec.rb
├── serializers
│ └── spree
│ │ ├── role_serializer_spec.rb
│ │ ├── user_serializer_spec.rb
│ │ ├── state_serializer_spec.rb
│ │ ├── taxonomy_serializer_spec.rb
│ │ ├── price_serializer_spec.rb
│ │ ├── option_type_serializer_spec.rb
│ │ ├── country_serializer_spec.rb
│ │ ├── store_serializer_spec.rb
│ │ ├── option_value_serializer_spec.rb
│ │ ├── image_serializer_spec.rb
│ │ ├── address_serializer_spec.rb
│ │ ├── line_item_serializer_spec.rb
│ │ ├── product_serializer_spec.rb
│ │ ├── taxon_serializer_spec.rb
│ │ ├── variant_serializer_spec.rb
│ │ ├── order_serializer_spec.rb
│ │ └── error_serializer_spec.rb
├── controllers
│ └── spree
│ │ └── api
│ │ └── v2
│ │ ├── countries_controller_spec.rb
│ │ ├── children_controller_spec.rb
│ │ ├── taxonomies_controller_spec.rb
│ │ ├── states_controller_spec.rb
│ │ ├── base_controller_spec.rb
│ │ ├── prices_controller_spec.rb
│ │ ├── taxons_controller_spec.rb
│ │ ├── orders_controller_spec.rb
│ │ ├── option_types_controller_spec.rb
│ │ ├── images_controller_spec.rb
│ │ ├── line_items_controller_spec.rb
│ │ ├── option_values_controller_spec.rb
│ │ ├── products_controller_spec.rb
│ │ └── variants_controller_spec.rb
└── spec_helper.rb
├── lib
├── solidus_json_api.rb
└── solidus_json_api
│ ├── config.rb
│ └── engine.rb
├── Gemfile
├── .gitignore
├── circle.yml
├── config
├── locales
│ └── en.yml
└── routes.rb
├── Rakefile
├── solidus_json_api.gemspec
├── LICENSE
├── README.md
└── CHANGELOG.md
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/docs/source/CNAME:
--------------------------------------------------------------------------------
1 | solidusapi.wildcardlabs.com
2 |
--------------------------------------------------------------------------------
/app/models/spree/order_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Order.class_eval do
2 | money_methods :promo_total
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/spree/price_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Price.class_eval do
2 | delegate :product, to: :variant
3 | end
4 |
--------------------------------------------------------------------------------
/docs/source/fonts/slate.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/fonts/slate.eot
--------------------------------------------------------------------------------
/docs/source/fonts/slate.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/fonts/slate.ttf
--------------------------------------------------------------------------------
/docs/source/fonts/slate.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/fonts/slate.woff
--------------------------------------------------------------------------------
/docs/source/fonts/slate.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/fonts/slate.woff2
--------------------------------------------------------------------------------
/docs/source/images/navbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/images/navbar.png
--------------------------------------------------------------------------------
/docs/source/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BenMorganIO/solidus_json_api/HEAD/docs/source/images/favicon.ico
--------------------------------------------------------------------------------
/docs/source/javascripts/all_nosearch.js:
--------------------------------------------------------------------------------
1 | //= require ./lib/_energize
2 | //= require ./app/_lang
3 | //= require ./app/_toc
4 |
--------------------------------------------------------------------------------
/app/models/spree/image_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Image.class_eval do
2 | scope :variants, -> { where(viewable_type: 'Spree::Variant') }
3 | end
4 |
--------------------------------------------------------------------------------
/docs/source/javascripts/all.js:
--------------------------------------------------------------------------------
1 | //= require ./lib/_energize
2 | //= require ./app/_lang
3 | //= require ./app/_search
4 | //= require ./app/_toc
5 |
--------------------------------------------------------------------------------
/spec/models/spree/price_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Price do
2 | it { is_expected.to delegate_method(:product).to :variant }
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/spree/line_item_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::LineItem.class_eval do
2 | before_save -> { order.send :validate_line_item_availability }
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/spree/state_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::State.class_eval do
2 | scope :find_country, -> (id) { includes(:country).find(id).country }
3 | end
4 |
--------------------------------------------------------------------------------
/app/serializers/spree/role_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class RoleSerializer < Spree::BaseSerializer
3 | attributes :name
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/serializers/spree/user_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class UserSerializer < Spree::BaseSerializer
3 | attributes :id, :email
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/solidus_json_api.rb:
--------------------------------------------------------------------------------
1 | require 'solidus_core'
2 | require 'active_model_serializers'
3 |
4 | require 'solidus_json_api/config'
5 | require 'solidus_json_api/engine'
6 |
--------------------------------------------------------------------------------
/app/models/spree/base_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Base.class_eval do
2 | def self.paginate(page_attrs)
3 | page(page_attrs[:number]).per(page_attrs[:size])
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'solidus_api', github: 'solidusio/solidus'
4 | gem 'solidus_auth_devise', github: 'solidusio/solidus_auth_devise'
5 |
6 | gemspec
7 |
--------------------------------------------------------------------------------
/app/serializers/spree/state_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class StateSerializer < Spree::BaseSerializer
3 | attributes :name, :abbr
4 |
5 | belongs_to :country
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/support/shoulda_matchers.rb:
--------------------------------------------------------------------------------
1 | Shoulda::Matchers.configure do |config|
2 | config.integrate do |with|
3 | with.test_framework :rspec
4 | with.library :rails
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | \#*
2 | *~
3 | .#*
4 | .DS_Store
5 | .idea
6 | .project
7 | .sass-cache
8 | coverage
9 | Gemfile.lock
10 | tmp
11 | nbproject
12 | pkg
13 | *.swp
14 | spec/dummy
15 | build
16 |
--------------------------------------------------------------------------------
/app/serializers/spree/taxonomy_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class TaxonomySerializer < Spree::BaseSerializer
3 | attributes :name, :position
4 |
5 | has_many :taxons
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/solidus_json_api/config.rb:
--------------------------------------------------------------------------------
1 | module SolidusJsonApi
2 | mattr_accessor :parent_serializer
3 | @@parent_serializer = ActiveModel::Serializer
4 |
5 | def self.setup
6 | yield self
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | ---
2 | machine:
3 | environment:
4 | DB: postgres
5 | services:
6 | - postgresql
7 | ruby:
8 | version: 2.3.0
9 |
10 | test:
11 | override:
12 | - bundle exec rake
13 |
--------------------------------------------------------------------------------
/app/serializers/spree/country_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class CountrySerializer < Spree::BaseSerializer
3 | attributes :iso_name, :iso, :iso3, :name, :numcode, :states_required
4 |
5 | has_many :states
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/serializers/spree/price_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class PriceSerializer < Spree::BaseSerializer
3 | attributes :amount, :price, :display_amount, :display_price, :currency
4 |
5 | belongs_to :variant
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/docs/Rakefile:
--------------------------------------------------------------------------------
1 | ENV['MM_ROOT'] = File.expand_path '.'
2 | ENV['BUNDLE_GEMFILE'] = File.expand_path './Gemfile'
3 |
4 | require 'middleman-gh-pages'
5 | require 'rake/clean'
6 |
7 | CLOBBER.include('build')
8 |
9 | task default: [:build]
10 |
--------------------------------------------------------------------------------
/app/serializers/spree/option_type_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class OptionTypeSerializer < Spree::BaseSerializer
3 | attributes :name, :presentation, :position
4 |
5 | has_many :option_values
6 | has_many :products
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/serializers/spree/option_value_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class OptionValueSerializer < Spree::BaseSerializer
3 | attributes :name, :presentation, :position
4 |
5 | belongs_to :option_type
6 | has_many :variants
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/base_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class BaseController < Spree::Api::BaseController
5 | include Spree::Api::V2::Renderable
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/serializers/spree/store_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class StoreSerializer < Spree::BaseSerializer
3 | attributes :name, :url, :meta_description, :meta_keywords, :seo_title,
4 | :mail_from_address, :default_currency, :code, :default
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/serializers/spree/image_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class ImageSerializer < Spree::BaseSerializer
3 | attributes :position, :alt, :links
4 |
5 | belongs_to :viewable
6 |
7 | def links
8 | image_links object.attachment
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/serializers/spree/address_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class AddressSerializer < Spree::BaseSerializer
3 | attributes :first_name, :last_name, :address1, :address2, :city, :zipcode,
4 | :phone
5 |
6 | belongs_to :state
7 | belongs_to :country
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Documentation for the Solidus JSON API
2 |
3 | The documentation is hand written and forked from [Slate](https://github.com/tripit/slate).
4 | Please update these docs when you make a contribution.
5 |
6 | ## Setup
7 |
8 | ```shell
9 | cd docs
10 | bundle
11 | middleman server
12 | ```
13 |
--------------------------------------------------------------------------------
/app/serializers/spree/product_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class ProductSerializer < Spree::BaseSerializer
3 | attributes :name, :description, :slug, :meta_description, :meta_keywords
4 |
5 | has_one :master
6 |
7 | has_many :variants
8 | has_many :taxons
9 | has_many :option_types
10 | has_many :images
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/docs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:trusty
2 |
3 | RUN apt-get update
4 | RUN apt-get install -yq ruby ruby-dev build-essential git
5 | RUN gem install --no-ri --no-rdoc bundler
6 | ADD Gemfile /app/Gemfile
7 | ADD Gemfile.lock /app/Gemfile.lock
8 | RUN cd /app; bundle install
9 | ADD . /app
10 | EXPOSE 4567
11 | WORKDIR /app
12 | CMD ["bundle", "exec", "middleman", "server"]
13 |
--------------------------------------------------------------------------------
/app/serializers/spree/variant_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class VariantSerializer < Spree::BaseSerializer
3 | attributes :sku, :weight, :height, :width, :depth, :is_master, :position,
4 | :name, :price, :display_price
5 |
6 | has_many :prices
7 | has_many :option_values
8 | has_many :images
9 |
10 | belongs_to :product
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/lib/solidus_json_api/config_spec.rb:
--------------------------------------------------------------------------------
1 | describe SolidusJsonApi do
2 | describe '.setup' do
3 | it 'can set the parent serializer' do
4 | class DescendantOfAMS < ActiveModel::Serializer; end
5 |
6 | described_class.setup { |c| c.parent_serializer = DescendantOfAMS }
7 | expect(described_class.parent_serializer).to eql DescendantOfAMS
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | ruby '2.2.3'
3 |
4 | # Middleman
5 | gem 'middleman', '~> 3.3.10'
6 | gem 'middleman-gh-pages', '~> 0.0.3'
7 | gem 'middleman-syntax', '~> 2.0.0'
8 | gem 'middleman-autoprefixer', '~> 2.4.4'
9 |
10 | gem 'rouge', '~> 1.9.0'
11 | gem 'redcarpet', '~> 3.3.2'
12 |
13 | gem 'rake', '~> 10.4.2'
14 | gem 'therubyracer', '~> 0.12.2', platforms: :ruby
15 |
--------------------------------------------------------------------------------
/app/serializers/spree/base_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class BaseSerializer < SolidusJsonApi.parent_serializer
3 | def image_links(object)
4 | {
5 | original: object.url(:original),
6 | mini: object.url(:mini),
7 | small: object.url(:small),
8 | product: object.url(:product),
9 | large: object.url(:large)
10 | }
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/serializers/spree/line_item_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class LineItemSerializer < Spree::BaseSerializer
3 | attributes :id, :quantity, :variant_id, :order_id, :currency, :cost_price,
4 | :adjustment_total, :additional_tax_total, :price
5 |
6 | %w(amount total).each do |money_method|
7 | attributes money_method, "display_#{money_method}"
8 | end
9 |
10 | belongs_to :order
11 | belongs_to :variant
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/serializers/spree/taxon_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class TaxonSerializer < Spree::BaseSerializer
3 | attributes :name, :permalink, :position, :description, :meta_title, :links,
4 | :meta_description, :meta_keywords, :depth
5 |
6 | belongs_to :taxonomy
7 | belongs_to :parent, serializer: TaxonSerializer
8 | has_many :children, serializer: TaxonSerializer
9 |
10 | def links
11 | image_links object.icon
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/models/spree/base_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Base do
2 | it '.paginate' do
3 | # The pagination is concerned onto abstract models. There's not really a way
4 | # to be able to ensure that the model is paginated, so instead of ensuring
5 | # that the result is paginated (tested in the Spree::Api::BaseController),
6 | # this just makes sure it can respond to the +.paginate+ method.
7 | expect(described_class).to respond_to(:paginate).with(1).argument
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/serializers/spree/role_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::RoleSerializer do
2 | let(:role) { create :role }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new role) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "name" : "#{role.name}"
11 | },
12 | "type" : "spree_roles"
13 | }
14 | }
15 | JSON
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/serializers/spree/user_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::UserSerializer do
2 | let(:user) { create :user }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new user) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "email" : "#{user.email}"
11 | },
12 | "type" : "spree_users"
13 | }
14 | }
15 | JSON
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/docs/source/includes/_pagination.md:
--------------------------------------------------------------------------------
1 | # Pagination
2 |
3 | ```shell
4 | curl "https://example.com/api/v2/kitten?page[number]2&page[size]=20"
5 | ```
6 |
7 | This projects supports the [JSON API's `page` keyword](http://jsonapi.org/format/#fetching-pagination).
8 | To specify the page number, set a `page[number]` value.
9 | To specify the page size (the amount of records returned), set a `page[size]` value; the default is 24.
10 | If you would like the links to the `self`, `next`, `prev`, `first`, and `last` it is not yet supported.
11 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/orders_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class OrdersController < Spree::Api::V2::BaseController
5 | def index
6 | render_collection orders
7 | end
8 |
9 | def show
10 | render_instance orders.find(params[:id])
11 | end
12 |
13 | private
14 |
15 | def orders
16 | @current_api_user.admin? ? Spree::Order : @current_api_user.orders
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/solidus_json_api/engine.rb:
--------------------------------------------------------------------------------
1 | module SolidusJsonApi
2 | class Engine < Rails::Engine
3 | require 'spree/core'
4 |
5 | isolate_namespace Spree
6 | engine_name 'solidus_json_api'
7 |
8 | # use rspec for tests
9 | config.generators do |g|
10 | g.test_framework :rspec
11 | end
12 |
13 | def self.activate
14 | Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
15 | Rails.configuration.cache_classes ? require(c) : load(c)
16 | end
17 | end
18 |
19 | config.to_prepare &method(:activate).to_proc
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/docs/source/includes/_filtering.md:
--------------------------------------------------------------------------------
1 | # Filtering
2 |
3 | ```shell
4 | curl "https://example.com/api/v2/kittens?filter[id]=1"
5 | curl "https://example.com/api/v2/kittens?filter[id]=2,3,4"
6 | curl "https://example.com/api/v2/kittens?filter[breed]=Persian,British%20Shorthair,Bengal"
7 | ```
8 |
9 | This project supports the [JSON API's `filter` keyword](http://jsonapi.org/format/#fetching-filtering) with only the root object, not any of its relationships yet.
10 | You can filter any of the attributes that is inside of the data object.
11 | Please see the example requests to the side on how you my filter a kitten object.
12 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/countries_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class CountriesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection Spree::Country.includes(:states)
9 | end
10 |
11 | def show
12 | if params[:state_id].present?
13 | render_instance Spree::State.find_country(params[:state_id])
14 | else
15 | render_instance Spree::Country.find(params[:id])
16 | end
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/taxonomies_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class TaxonomiesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection Spree::Taxonomy.includes(:taxons)
9 | end
10 |
11 | def show
12 | if params[:taxon_id].present?
13 | render_instance Spree::Taxon.find(params[:taxon_id]).taxonomy
14 | else
15 | render_instance Spree::Taxonomy.find(params[:id])
16 | end
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/prices_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class PricesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user
6 |
7 | def index
8 | render_collection params[:variant_id] ? prices : prices.includes(:variant)
9 | end
10 |
11 | def show
12 | render_instance prices.find(params[:id])
13 | end
14 |
15 | private
16 |
17 | def prices
18 | if params[:variant_id]
19 | Spree::Variant.find(params[:variant_id]).prices
20 | else
21 | Spree::Price
22 | end
23 | end
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/serializers/spree/state_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::StateSerializer do
2 | let(:state) { create :state }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new state) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "abbr" : "#{state.abbr}",
11 | "name" : "#{state.name}"
12 | },
13 | "relationships" : {
14 | "country" : {
15 | "data" : {
16 | "type" : "spree_countries"
17 | }
18 | }
19 | },
20 | "type" : "spree_states"
21 | }
22 | }
23 | JSON
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/serializers/spree/order_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class OrderSerializer < Spree::BaseSerializer
3 | attributes :email, :number, :state, :completed_at, :shipment_state,
4 | :payment_state, :special_instructions, :currency, :channel,
5 | :item_count, :approved_at, :confirmation_delivered, :canceled_at,
6 | :payment_total, :total, :display_total
7 |
8 | %w(
9 | item adjustment shipment promo additional_tax included_tax
10 | ).each do |money_method|
11 | attributes "#{money_method}_total", "display_#{money_method}_total"
12 | end
13 |
14 | has_one :user
15 | has_one :bill_address
16 | has_one :ship_address
17 |
18 | has_many :line_items
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/states_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class StatesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user
6 |
7 | def index
8 | render_collection states
9 | end
10 |
11 | def show
12 | render_instance states.find(params[:id])
13 | end
14 |
15 | private
16 |
17 | def states
18 | @states ||= begin
19 | if params[:country_id].present?
20 | Spree::Country.find(params[:country_id]).states.includes(:country)
21 | else
22 | Spree::State.includes(:country)
23 | end
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/children_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class ChildrenController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection children.includes(:products, :taxonomy, :children, :parent)
9 | end
10 |
11 | def show
12 | render_instance children.find(params[:id])
13 | end
14 |
15 | private
16 |
17 | def children
18 | if params[:taxon_id]
19 | Spree::Taxon.find(params[:taxon_id])
20 | end.children
21 | end
22 |
23 | def filter_attributes
24 | Spree::TaxonSerializer._attributes
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/serializers/spree/taxonomy_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::TaxonomySerializer do
2 | let(:taxonomy) { create :taxonomy }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new taxonomy) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "name" : "#{taxonomy.name}",
11 | "position" : #{taxonomy.position}
12 | },
13 | "relationships" : {
14 | "taxons" : {
15 | "data" : [
16 | {
17 | "type" : "spree_taxons"
18 | }
19 | ]
20 | }
21 | },
22 | "type" : "spree_taxonomies"
23 | }
24 | }
25 | JSON
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/images_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class ImagesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection images.includes(:viewable)
9 | end
10 |
11 | def show
12 | render_instance images.find(params[:id])
13 | end
14 |
15 | private
16 |
17 | def images
18 | if params[:product_id]
19 | Spree::Product.find(params[:product_id]).images
20 | elsif params[:variant_id]
21 | Spree::Variant.find(params[:variant_id]).images
22 | else
23 | Spree::Image
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/countries_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::CountriesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:country) { create :country }
5 | let(:state) { create :state, country: country }
6 |
7 | describe '#index' do
8 | it 'will list all countries' do
9 | get :index
10 | expect(JSON.parse(response.body)['data'].length).to eql 1
11 | end
12 | end
13 |
14 | describe '#show' do
15 | it 'will list the selected country' do
16 | get :show, id: country.id
17 | expect(JSON.parse(response.body)['data']['id']).to eql country.id.to_s
18 | end
19 |
20 | it 'will the country via a state' do
21 | get :show, state_id: state.id
22 | expect(JSON.parse(response.body)['data']['id']).to eql country.id.to_s
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/serializers/spree/price_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::PriceSerializer do
2 | let(:price) { create :price }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new price) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "amount" : "19.99",
11 | "currency" : "USD",
12 | "display_amount" : "$19.99",
13 | "display_price" : "$19.99",
14 | "price" : "19.99"
15 | },
16 | "relationships" : {
17 | "variant" : {
18 | "data" : {
19 | "type" : "spree_variants"
20 | }
21 | }
22 | },
23 | "type" : "spree_prices"
24 | }
25 | }
26 | JSON
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/serializers/spree/option_type_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::OptionTypeSerializer do
2 | let(:option_type) { create :option_type }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new option_type) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "name" : "#{option_type.name}",
11 | "position" : #{option_type.position},
12 | "presentation" : "#{option_type.presentation}"
13 | },
14 | "relationships" : {
15 | "option_values" : {
16 | "data" : []
17 | },
18 | "products" : {
19 | "data" : []
20 | }
21 | },
22 | "type" : "spree_option_types"
23 | }
24 | }
25 | JSON
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/docs/source/includes/_errors.md:
--------------------------------------------------------------------------------
1 | # Errors
2 |
3 | The Solidus JSON API uses the following error codes:
4 |
5 | Error Code | Meaning
6 | ---------- | -------
7 | 400 | Bad Request -- Your request sucks
8 | 401 | Unauthorized -- Your API key is wrong
9 | 403 | Forbidden -- The kitten requested is hidden for administrators only
10 | 404 | Not Found -- The specified kitten could not be found
11 | 405 | Method Not Allowed -- You tried to access a kitten with an invalid method
12 | 406 | Not Acceptable -- You requested a format that isn't json
13 | 410 | Gone -- The kitten requested has been removed from our servers
14 | 418 | I'm a teapot
15 | 429 | Too Many Requests -- You're requesting too many kittens! Slow down!
16 | 500 | Internal Server Error -- We had a problem with our server. Try again later.
17 | 503 | Service Unavailable -- We're temporarially offline for maintanance. Please try again later.
18 |
--------------------------------------------------------------------------------
/spec/serializers/spree/country_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::CountrySerializer do
2 | let(:country) { create :country }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new country) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "iso" : "#{country.iso}",
11 | "iso3" : "#{country.iso3}",
12 | "iso_name" : "#{country.iso_name}",
13 | "name" : "#{country.name}",
14 | "numcode" : #{country.numcode},
15 | "states_required" : #{country.states_required}
16 | },
17 | "relationships" : {
18 | "states" : {
19 | "data" : []
20 | }
21 | },
22 | "type" : "spree_countries"
23 | }
24 | }
25 | JSON
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/option_values_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class OptionValuesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection option_values.includes(:option_type, :variants)
9 | end
10 |
11 | def show
12 | render_instance option_values.find(params[:id])
13 | end
14 |
15 | private
16 |
17 | def option_values
18 | if params[:option_type_id]
19 | Spree::OptionType.find(params[:option_type_id]).option_values
20 | elsif params[:variant_id]
21 | Spree::Variant.find(params[:variant_id]).option_values
22 | else
23 | Spree::OptionValue
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/serializers/spree/store_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::StoreSerializer do
2 | let(:store) { create :store }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new store) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "code" : "#{store.code}",
11 | "default" : #{store.default},
12 | "default_currency" : #{store.default_currency || 'null'},
13 | "mail_from_address" : "#{store.mail_from_address}",
14 | "meta_description" : null,
15 | "meta_keywords" : null,
16 | "name" : "#{store.name}",
17 | "seo_title" : null,
18 | "url" : "#{store.url}"
19 | },
20 | "type" : "spree_stores"
21 | }
22 | }
23 | JSON
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/children_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::ChildrenController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let(:taxon) { create :taxon }
5 | let!(:child) { create :taxon, parent: taxon, taxonomy: taxon.taxonomy }
6 |
7 | describe '#index' do
8 | context 'by taxon' do
9 | it 'can list all the children' do
10 | get :index, taxon_id: taxon.id
11 | taxon_ids = parse_json(response.body)['data'].map do |taxon|
12 | taxon['id']
13 | end
14 | expect(taxon_ids).to include child.id.to_s
15 | end
16 | end
17 | end
18 |
19 | describe '#show' do
20 | context 'by taxon' do
21 | it 'will fetch the selected child' do
22 | get :show, taxon_id: taxon.id, id: child.id
23 | child_id = parse_json(response.body)['data']['id']
24 | expect(child_id).to eql child.id.to_s
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/option_types_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class OptionTypesController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection option_types.includes(:option_values, :products)
9 | end
10 |
11 | def show
12 | if params[:option_value_id]
13 | render_instance Spree::OptionValue.find(params[:option_value_id]).option_type
14 | else
15 | render_instance option_types.find(params[:id])
16 | end
17 | end
18 |
19 | private
20 |
21 | def option_types
22 | if params[:product_id]
23 | Spree::Product.find(params[:product_id]).option_types
24 | else
25 | Spree::OptionType
26 | end
27 | end
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/docs/source/stylesheets/_syntax.scss.erb:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | */
16 |
17 | @import 'variables';
18 |
19 | <%= Rouge::Themes::Base16::Monokai.render(:scope => '.highlight') %>
20 |
21 | .highlight .c, .highlight .cm, .highlight .c1, .highlight .cs {
22 | color: #909090;
23 | }
24 |
25 | .highlight, .highlight .w {
26 | background-color: $code-bg;
27 | }
--------------------------------------------------------------------------------
/spec/serializers/spree/option_value_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::OptionValueSerializer do
2 | let(:option_value) { create :option_value }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new option_value) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "name" : "#{option_value.name}",
11 | "position" : #{option_value.position},
12 | "presentation" : "#{option_value.presentation}"
13 | },
14 | "relationships" : {
15 | "option_type" : {
16 | "data" : {
17 | "type" : "spree_option_types"
18 | }
19 | },
20 | "variants" : {
21 | "data" : []
22 | }
23 | },
24 | "type" : "spree_option_values"
25 | }
26 | }
27 | JSON
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | spree:
3 | api:
4 | errors:
5 | invalid_token:
6 | detail: 'Your token is invalid, please use a different one.'
7 | title: Invalid Token
8 | order_complete:
9 | detail: This order has been completed.
10 | title: Order Complete
11 | product_out_of_stock:
12 | title: Product is out of Stock
13 | detail: >
14 | This product is out of stock for the selected quantity.
15 | record_not_found:
16 | title: Record Not Found
17 | detail: >
18 | One of the records that you were looking for could not be found.
19 | Please check to see if the record exists or if you're permitted to
20 | read it.
21 | quantity_too_high:
22 | title: Quantity is too High
23 | detail: >
24 | The quantity that you have submitted is astronomically high, please
25 | tone it down a bit.
26 |
--------------------------------------------------------------------------------
/docs/source/stylesheets/_icon-font.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'slate';
3 | src:font-url('slate.eot?-syv14m');
4 | src:font-url('slate.eot?#iefix-syv14m') format('embedded-opentype'),
5 | font-url('slate.woff2?-syv14m') format('woff2'),
6 | font-url('slate.woff?-syv14m') format('woff'),
7 | font-url('slate.ttf?-syv14m') format('truetype'),
8 | font-url('slate.svg?-syv14m#slate') format('svg');
9 | font-weight: normal;
10 | font-style: normal;
11 | }
12 |
13 | %icon {
14 | font-family: 'slate';
15 | speak: none;
16 | font-style: normal;
17 | font-weight: normal;
18 | font-variant: normal;
19 | text-transform: none;
20 | line-height: 1;
21 | }
22 |
23 | %icon-exclamation-sign {
24 | @extend %icon;
25 | content: "\e600";
26 | }
27 | %icon-info-sign {
28 | @extend %icon;
29 | content: "\e602";
30 | }
31 | %icon-ok-sign {
32 | @extend %icon;
33 | content: "\e606";
34 | }
35 | %icon-search {
36 | @extend %icon;
37 | content: "\e607";
38 | }
39 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/taxonomies_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::TaxonomiesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:taxonomy) { create :taxonomy }
5 |
6 | describe '#index' do
7 | it 'will list taxonomies' do
8 | get :index
9 | taxonomy_ids = parse_json(response.body)['data'].map do |taxonomy|
10 | taxonomy['id']
11 | end
12 | expect(taxonomy_ids).to include taxonomy.id.to_s
13 | end
14 | end
15 |
16 | describe '#show' do
17 | let(:taxon) { taxonomy.root }
18 |
19 | it 'will show taxonomy' do
20 | get :show, id: taxonomy.id
21 | taxonomy_id = parse_json(response.body)['data']['id']
22 | expect(taxonomy_id).to eql taxonomy.id.to_s
23 | end
24 |
25 | it 'can show taxonomy by taxon_id' do
26 | get :show, taxon_id: taxon.id
27 | taxonomy_id = parse_json(response.body)['data']['id']
28 | expect(taxonomy_id).to eql taxonomy.id.to_s
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/serializers/spree/image_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::ImageSerializer do
2 | let(:image) { create :image }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new image) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "alt" : null,
11 | "links" : {
12 | "large" : "#{image.attachment.url(:large)}",
13 | "mini" : "#{image.attachment.url(:mini)}",
14 | "original" : "#{image.attachment.url(:original)}",
15 | "product" : "#{image.attachment.url(:product)}",
16 | "small" : "#{image.attachment.url(:small)}"
17 | },
18 | "position" : 1
19 | },
20 | "relationships" : {
21 | "viewable" : {
22 | "data" : null
23 | }
24 | },
25 | "type" : "spree_images"
26 | }
27 | }
28 | JSON
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/docs/config.rb:
--------------------------------------------------------------------------------
1 | PROJECT_ROOT = File.expand_path '.'
2 | BUILD_DIR = File.expand_path './../build'
3 |
4 | # Markdown
5 | set :markdown_engine, :redcarpet
6 | set :markdown,
7 | fenced_code_blocks: true,
8 | smartypants: true,
9 | disable_indented_code_blocks: true,
10 | prettify: true,
11 | tables: true,
12 | with_toc_data: true,
13 | no_intra_emphasis: true
14 |
15 | # Assets
16 | set :css_dir, 'stylesheets'
17 | set :js_dir, 'javascripts'
18 | set :images_dir, 'images'
19 | set :fonts_dir, 'fonts'
20 |
21 | # Activate the syntax highlighter
22 | activate :syntax
23 |
24 | activate :autoprefixer do |config|
25 | config.browsers = ['last 2 version', 'Firefox ESR']
26 | config.cascade = false
27 | config.inline = true
28 | end
29 |
30 | # Github pages require relative links
31 | activate :relative_assets
32 | set :relative_links, true
33 |
34 | # Build Configuration
35 | set :build_dir, BUILD_DIR
36 | configure :build do
37 | activate :minify_css
38 | activate :minify_javascript
39 | end
40 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 |
3 | Bundler::GemHelper.install_tasks
4 |
5 | require 'rspec/core/rake_task'
6 |
7 | require 'generators/spree/dummy/dummy_generator'
8 | require 'generators/spree/install/install_generator'
9 |
10 | RSpec::Core::RakeTask.new
11 |
12 | task :default do
13 | if Dir['spec/dummy'].empty?
14 | Rake::Task[:test_app].invoke
15 | Dir.chdir('../../')
16 | end
17 | Rake::Task[:spec].invoke
18 | end
19 |
20 | desc 'Generates a dummy app for testing'
21 | task :test_app do
22 | ENV["RAILS_ENV"] = 'test'
23 |
24 | Spree::DummyGeneratorHelper.inject_extension_requirements = true
25 | Spree::DummyGenerator.start %w(--lib_name=solidus_json_api --quiet)
26 |
27 | Spree::InstallGenerator.class_eval do
28 | def config_spree_yml() end
29 | end
30 |
31 | Spree::InstallGenerator.start %w(
32 | --lib_name=solidus_json_api --auto-accept --migrate=false --seed=false
33 | --sample=false --quiet --user_class=Spree::LegacyUser
34 | )
35 |
36 | puts 'Setting up dummy database...'
37 | system "bundle exec rake db:drop db:create db:migrate > #{File::NULL}"
38 | end
39 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/taxons_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class TaxonsController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection taxons
9 | end
10 |
11 | def show
12 | render_instance taxon
13 | end
14 |
15 | private
16 |
17 | def taxon
18 | if params[:taxon_id].present?
19 | taxons.includes(parent: taxon_associations).find(params[:taxon_id]).parent
20 | else
21 | taxons.find(params[:id])
22 | end
23 | end
24 |
25 | def taxons
26 | if params[:taxonomy_id].present?
27 | Spree::Taxonomy.find(params[:taxonomy_id]).taxons
28 | .includes(*taxon_associations.-([:taxonomy]))
29 | else
30 | Spree::Taxon.includes(*taxon_associations)
31 | end
32 | end
33 |
34 | def taxon_associations
35 | [:products, :taxonomy, :parent]
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/serializers/spree/address_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::AddressSerializer do
2 | let(:address) { create :address }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new address) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "address1" : "#{address.address1}",
11 | "address2" : "#{address.address2}",
12 | "city" : "#{address.city}",
13 | "first_name" : "#{address.first_name}",
14 | "last_name" : "#{address.last_name}",
15 | "phone" : "#{address.phone}",
16 | "zipcode" : "#{address.zipcode}"
17 | },
18 | "relationships" : {
19 | "country" : {
20 | "data" : {
21 | "type" : "spree_countries"
22 | }
23 | },
24 | "state" : {
25 | "data" : {
26 | "type" : "spree_states"
27 | }
28 | }
29 | },
30 | "type" : "spree_addresses"
31 | }
32 | }
33 | JSON
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/variants_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class VariantsController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user
6 |
7 | def index
8 | render_collection variants.includes(:default_price, :prices, :option_values, :product, :images)
9 | end
10 |
11 | def show
12 | if params[:price_id]
13 | render_instance Spree::Price.find(params[:price_id]).variant
14 | elsif params[:image_id]
15 | render_instance Spree::Image.variants.find(params[:image_id]).viewable
16 | else
17 | render_instance variants.find(params[:id])
18 | end
19 | end
20 |
21 | private
22 |
23 | def variants
24 | if params[:product_id]
25 | Spree::Product.find(params[:product_id]).variants_including_master
26 | elsif params[:option_value_id]
27 | Spree::OptionValue.find(params[:option_value_id]).variants
28 | else
29 | Spree::Variant
30 | end
31 | end
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/line_items_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class LineItemsController < Spree::Api::V2::BaseController
5 | rescue_from ActiveRecord::RecordNotFound, with: :render_record_not_found_error
6 | rescue_from Spree::Order::InsufficientStock, with: :render_insufficient_stock_error
7 | rescue_from RangeError, with: :render_range_error
8 |
9 | def create
10 | variant = Spree::Variant.find(line_item_params[:variant_id])
11 | order = @current_api_user.orders.find(line_item_params[:order_id])
12 | line_item = order.contents.add(variant, line_item_params[:quantity])
13 | render_instance line_item
14 | end
15 |
16 | private
17 |
18 | def line_item_params
19 | params.require(:data).require(:attributes).permit(:variant_id, :order_id, :quantity)
20 | end
21 |
22 | def render_range_error
23 | render_error :quantity_too_high
24 | end
25 |
26 | def render_insufficient_stock_error
27 | render_error :product_out_of_stock
28 | end
29 |
30 | def render_record_not_found_error
31 | render_error :record_not_found
32 | end
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v2/products_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | class ProductsController < Spree::Api::V2::BaseController
5 | skip_before_action :authenticate_user, only: [:index, :show]
6 |
7 | def index
8 | render_collection products.includes :variants, :master, :taxons, :option_types, master: :images
9 | end
10 |
11 | def show
12 | if params[:price_id]
13 | render_instance Spree::Price.find(params[:price_id]).product
14 | elsif params[:variant_id]
15 | render_instance Spree::Variant.find(params[:variant_id]).product
16 | elsif params[:image_id]
17 | render_instance Spree::Image.variants.find(params[:image_id]).viewable.product
18 | else
19 | render_instance products.friendly.find(params[:id])
20 | end
21 | end
22 |
23 | private
24 |
25 | def products
26 | if params[:taxon_id].present?
27 | Spree::Taxon.find(params[:taxon_id]).products
28 | elsif params[:option_type_id]
29 | Spree::OptionType.find(params[:option_type_id]).products
30 | else
31 | Spree::Product
32 | end
33 | end
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/serializers/spree/line_item_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::LineItemSerializer do
2 | let(:line_item) { create :line_item }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new line_item) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "additional_tax_total" : "0.0",
11 | "adjustment_total" : "0.0",
12 | "amount" : "10.0",
13 | "cost_price" : "17.0",
14 | "currency" : "USD",
15 | "display_amount" : "$10.00",
16 | "display_total" : "$10.00",
17 | "order_id" : #{line_item.order_id},
18 | "price" : "10.0",
19 | "quantity" : 1,
20 | "total" : "10.0",
21 | "variant_id" : #{line_item.variant_id}
22 | },
23 | "relationships" : {
24 | "order" : {
25 | "data" : {
26 | "type" : "spree_orders"
27 | }
28 | },
29 | "variant" : {
30 | "data" : {
31 | "type" : "spree_variants"
32 | }
33 | }
34 | },
35 | "type" : "spree_line_items"
36 | }
37 | }
38 | JSON
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/serializers/spree/product_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::ProductSerializer do
2 | let(:image) { create :image }
3 | let(:product) { create :product }
4 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new product) }
5 |
6 | before { product.images << image }
7 |
8 | its(:to_json) do
9 | is_expected.to be_json_eql <<-JSON
10 | {
11 | "data" : {
12 | "attributes" : {
13 | "description" : #{product.description.to_json},
14 | "meta_description" : null,
15 | "meta_keywords" : null,
16 | "name" : "#{product.name}",
17 | "slug" : "#{product.slug}"
18 | },
19 | "relationships" : {
20 | "images" : {
21 | "data" : [
22 | {
23 | "type" : "spree_images"
24 | }
25 | ]
26 | },
27 | "master" : {
28 | "data" : {
29 | "type" : "spree_variants"
30 | }
31 | },
32 | "option_types" : {
33 | "data" : []
34 | },
35 | "taxons" : {
36 | "data" : []
37 | },
38 | "variants" : {
39 | "data" : []
40 | }
41 | },
42 | "type" : "spree_products"
43 | }
44 | }
45 | JSON
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/states_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::StatesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:state) { create :state }
5 |
6 | describe '#index' do
7 | it 'can list all states' do
8 | get :index
9 | expect(JSON.parse(response.body)['data'].length).to eql 1
10 | end
11 |
12 | context 'by country id' do
13 | it 'will list selected states by country' do
14 | get :index, country_id: state.country.id
15 | expect(JSON.parse(response.body)['data'].length).to eql 1
16 | end
17 |
18 | it 'will not include states belonging to other countries' do
19 | new_state = create :state
20 | get :index, country_id: state.country.id
21 | state_ids = JSON.parse(response.body)['data'].map do |states|
22 | states['id']
23 | end
24 | expect(state_ids).to_not include new_state
25 | end
26 | end
27 | end
28 |
29 | describe '#show' do
30 | it 'will find selected state by id' do
31 | get :show, id: state.id
32 | state_id = parse_json(response.body)['data']['id']
33 | expect(state_id).to eql state.id.to_s
34 | end
35 |
36 | it 'can find a state by a country' do
37 | get :show, country_id: state.country.id, id: state.id
38 | state_id = parse_json(response.body)['data']['id']
39 | expect(state_id).to eql state.id.to_s
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/serializers/spree/taxon_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::TaxonSerializer do
2 | let(:taxon) { create :taxon }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new taxon) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "depth" : 0,
11 | "description" : null,
12 | "links": {
13 | "large" : "/assets/default_taxon.png",
14 | "mini" : "/assets/default_taxon.png",
15 | "original" : "/assets/default_taxon.png",
16 | "product" : "/assets/default_taxon.png",
17 | "small" : "/assets/default_taxon.png"
18 | },
19 | "meta_description" : null,
20 | "meta_keywords" : null,
21 | "meta_title" : null,
22 | "name" : "#{taxon.name}",
23 | "permalink" : "#{taxon.permalink}",
24 | "position" : 0
25 | },
26 | "relationships" : {
27 | "children" : {
28 | "data" : []
29 | },
30 | "parent" : {
31 | "data" : null
32 | },
33 | "taxonomy" : {
34 | "data" : {
35 | "type" : "spree_taxonomies"
36 | }
37 | }
38 | },
39 | "type" : "spree_taxons"
40 | }
41 | }
42 | JSON
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/docs/source/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Solidus JSON API Reference
3 |
4 | language_tabs:
5 | - shell
6 |
7 | toc_footers:
8 | - Source Code
9 | - Spree API Documentation
10 | - JSON API Documentation
11 | - Documentation Powered by Slate
12 |
13 | includes:
14 | - errors
15 | - pagination
16 | - filtering
17 | - countries
18 | - images
19 | - line_items
20 | - option_types
21 | - option_values
22 | - orders
23 | - prices
24 | - products
25 | - states
26 | - taxonomies
27 | - taxons
28 | - variants
29 |
30 | search: true
31 | ---
32 |
33 | # Introduction
34 |
35 | Welcome to the Solidus JSON API!
36 | You can use our API to grab data from our database and make updates.
37 |
38 | We currently only have a language binding in Shell.
39 | You can view code examples in the dark area to the right.
40 |
41 | This example API documentation page was created with [Slate](http://github.com/tripit/slate).
42 | Feel free to edit it and use it as a base for documenting APIs of your own.
43 |
44 | This JSON API is formatted after the [JSON API spec](http://jsonapi.org).
45 | Please follow its guidelines to learn about:
46 |
47 | - Pagination
48 | - Including Relationships
49 | - Sorting
50 | - Error Objects
51 | - The Document Structure
52 |
--------------------------------------------------------------------------------
/solidus_json_api.gemspec:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | Gem::Specification.new do |s|
3 | s.platform = Gem::Platform::RUBY
4 | s.name = 'solidus_json_api'
5 | s.version = '0.3.1'
6 | s.summary = 'A JSON API for Solidus.'
7 | s.description = 'Adds an assortment of new api endpoints that are JSON API compatible.'
8 | s.required_ruby_version = '>= 2.0.0'
9 |
10 | s.author = 'Ben A. Morgan'
11 | s.email = 'ben@benmorgan.io'
12 | s.homepage = 'http://solidusapi.wildcardlabs.com'
13 |
14 | s.files = `git ls-files`.split("\n")
15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16 | s.require_path = 'lib'
17 | s.requirements << 'none'
18 |
19 | s.add_dependency 'solidus_api', '~> 1.0'
20 | s.add_dependency 'active_model_serializers', '= 0.10.0.rc4'
21 |
22 | s.add_development_dependency 'coffee-rails'
23 | s.add_development_dependency 'coveralls'
24 | s.add_development_dependency 'database_cleaner'
25 | s.add_development_dependency 'factory_girl', '~> 4.5'
26 | s.add_development_dependency 'ffaker'
27 | s.add_development_dependency 'rspec-rails', '~> 3.3'
28 | s.add_development_dependency 'rspec-its'
29 | s.add_development_dependency 'sass-rails', '~> 5.0.0.beta1'
30 | s.add_development_dependency 'simplecov'
31 | s.add_development_dependency 'sqlite3'
32 | s.add_development_dependency 'pg'
33 | s.add_development_dependency 'pry'
34 | s.add_development_dependency 'json_spec', '~> 1.1.4'
35 | s.add_development_dependency 'shoulda-matchers', '~> 3.0.0'
36 | end
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Ben A. Morgan
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
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 Solidus 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.
15 |
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.
27 |
--------------------------------------------------------------------------------
/docs/source/javascripts/app/_toc.js:
--------------------------------------------------------------------------------
1 | //= require ../lib/_jquery_ui
2 | //= require ../lib/_jquery.tocify
3 | //= require ../lib/_imagesloaded.min
4 | (function (global) {
5 | 'use strict';
6 |
7 | var closeToc = function() {
8 | $(".tocify-wrapper").removeClass('open');
9 | $("#nav-button").removeClass('open');
10 | };
11 |
12 | var makeToc = function() {
13 | global.toc = $("#toc").tocify({
14 | selectors: 'h1, h2',
15 | extendPage: false,
16 | theme: 'none',
17 | smoothScroll: false,
18 | showEffectSpeed: 0,
19 | hideEffectSpeed: 180,
20 | ignoreSelector: '.toc-ignore',
21 | highlightOffset: 60,
22 | scrollTo: -1,
23 | scrollHistory: true,
24 | hashGenerator: function (text, element) {
25 | return element.prop('id');
26 | }
27 | }).data('toc-tocify');
28 |
29 | $("#nav-button").click(function() {
30 | $(".tocify-wrapper").toggleClass('open');
31 | $("#nav-button").toggleClass('open');
32 | return false;
33 | });
34 |
35 | $(".page-wrapper").click(closeToc);
36 | $(".tocify-item").click(closeToc);
37 | };
38 |
39 | // Hack to make already open sections to start opened,
40 | // instead of displaying an ugly animation
41 | function animate() {
42 | setTimeout(function() {
43 | toc.setOption('showEffectSpeed', 180);
44 | }, 50);
45 | }
46 |
47 | $(function() {
48 | makeToc();
49 | animate();
50 | $('.content').imagesLoaded( function() {
51 | global.toc.calculateHeights();
52 | });
53 | });
54 | })(window);
55 |
56 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/base_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::BaseController do
2 | controller do
3 | skip_before_action :authenticate_user
4 |
5 | def index
6 | render_collection Spree::Order
7 | end
8 | end
9 |
10 | describe '#filter_params' do
11 | before do
12 | allow(controller).to receive(:serializer_attributes) { Spree::OrderSerializer._attributes }
13 | end
14 |
15 | let!(:orders) do
16 | [create(:order), create(:order), create(:order, frontend_viewable: false)]
17 | end
18 |
19 | it 'can filter by a serializer\'s attribute' do
20 | first_order = orders.first
21 | get :index, filter: { id: first_order.id }
22 | order_ids = parse_json(response.body)['data'].map do |order|
23 | order['id']
24 | end
25 | expect(order_ids).to match [first_order.id.to_s]
26 | end
27 |
28 | it 'can filter multiple attributes' do
29 | first_order, second_order, _third_order = orders
30 | get :index, filter: { id: [first_order.id, second_order.id] }
31 | order_ids = parse_json(response.body)['data'].map do |order|
32 | order['id'].to_i
33 | end
34 |
35 | expect(order_ids).to match [first_order.id, second_order.id]
36 | end
37 |
38 | it 'will allow filtering of non-serializer attributes' do
39 | get :index, filter: { frontend_viewable: false }
40 | order_ids = parse_json(response.body)['data'].map do |order|
41 | order['id'].to_i
42 | end
43 | expect(order_ids).to match orders.map(&:id)
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'simplecov'
2 |
3 | if ENV['CI']
4 | require 'coveralls'
5 | Coveralls.wear!('rails')
6 | end
7 |
8 | SimpleCov.start do
9 | add_filter 'spec/dummy'
10 | add_group 'Controllers', 'app/controllers'
11 | add_group 'Models', 'app/models'
12 | add_group 'Serializers', 'app/serializers'
13 | add_group 'Libraries', 'lib'
14 | end
15 |
16 | ENV['RAILS_ENV'] = 'test'
17 |
18 | require File.expand_path('../dummy/config/environment.rb', __FILE__)
19 |
20 | require 'rspec/rails'
21 | require 'rspec/its'
22 | require 'database_cleaner'
23 | require 'ffaker'
24 | require 'json_spec'
25 | require 'shoulda/matchers'
26 |
27 | Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
28 |
29 | require 'spree/testing_support/factories'
30 |
31 | RSpec.configure do |config|
32 | config.include FactoryGirl::Syntax::Methods
33 | config.include JsonSpec::Helpers
34 |
35 | config.infer_spec_type_from_file_location!
36 | config.mock_with :rspec
37 | config.color = true
38 | config.use_transactional_fixtures = false
39 | config.fail_fast = ENV['FAIL_FAST'] || false
40 | config.order = "random"
41 |
42 | config.before :suite do
43 | ActiveModel::Serializer.config.adapter = :json_api
44 |
45 | DatabaseCleaner.strategy = :transaction
46 | DatabaseCleaner.clean_with :truncation
47 | end
48 |
49 | config.before :each do
50 | DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
51 | DatabaseCleaner.start
52 | end
53 |
54 | config.after :each do
55 | DatabaseCleaner.clean
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/app/serializers/spree/error_serializer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class ErrorSerializer
3 | attr_accessor :resource, :options, :response
4 |
5 | def initialize(resource, options = {})
6 | @resource = resource
7 | @response = options.delete(:response)
8 | @options = options
9 | end
10 |
11 | def as_json
12 | { errors: error_messages }.to_json
13 | end
14 |
15 | private
16 |
17 | def resource_error_objects(messages)
18 | messages.map do |title, detail|
19 | pointer = "data/attributes/#{title.to_s.split('.').join('/')}"
20 | error_object title.to_s.gsub('.', ' '), detail.to_sentence, pointer: pointer
21 | end
22 | end
23 |
24 | def error_messages
25 | case resource
26 | when Symbol
27 | title_translation = Spree.t("api.errors.#{resource}.title")
28 | detail_translation = Spree.t("api.errors.#{resource}.detail")
29 | [error_object(title_translation, detail_translation)]
30 | when Array
31 | resource.map { |errors| resource_error_objects errors }.flatten
32 | else
33 | resource_error_objects resource.errors.messages
34 | end
35 | end
36 |
37 | def error_object(title, detail, pointer: '')
38 | error = {
39 | detail: detail.gsub("\n", ' ').strip,
40 | meta: options,
41 | title: title.to_s.titleize
42 | }
43 |
44 | error.merge!(response_attributes) if response.present?
45 | pointer.present? ? error.merge(pointer: pointer) : error
46 | end
47 |
48 | def response_attributes
49 | {
50 | status: response.status_message,
51 | code: response.code
52 | }
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/serializers/spree/variant_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::VariantSerializer do
2 | let(:image) { create :image }
3 | let(:variant) { create :variant, images: [image] }
4 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new variant) }
5 |
6 | its(:to_json) do
7 | is_expected.to be_json_eql <<-JSON
8 | {
9 | "data" : {
10 | "attributes" : {
11 | "depth" : #{variant.depth.to_json},
12 | "display_price" : "#{variant.display_price}",
13 | "height" : #{variant.height.to_json},
14 | "is_master" : #{variant.is_master},
15 | "name" : "#{variant.name}",
16 | "position" : #{variant.position},
17 | "price" : "#{variant.price}",
18 | "sku" : "#{variant.sku}",
19 | "weight" : "#{variant.weight}",
20 | "width" : #{variant.width.to_json}
21 | },
22 | "relationships" : {
23 | "images" : {
24 | "data" : [
25 | {
26 | "type" : "spree_images"
27 | }
28 | ]
29 | },
30 | "option_values" : {
31 | "data" : [
32 | {
33 | "type" : "spree_option_values"
34 | }
35 | ]
36 | },
37 | "prices" : {
38 | "data" : [
39 | {
40 | "type" : "spree_prices"
41 | }
42 | ]
43 | },
44 | "product" : {
45 | "data" : {
46 | "type" : "spree_products"
47 | }
48 | }
49 | },
50 | "type" : "spree_variants"
51 | }
52 | }
53 | JSON
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Spree::Core::Engine.routes.draw do
2 | namespace :api, defaults: { format: :json } do
3 | namespace :v2 do
4 | resources :countries, only: [:index, :show] do
5 | resources :states, only: [:index, :show]
6 | end
7 |
8 | resources :images, only: [:index, :show] do
9 | resource :product, :variant, only: :show
10 | end
11 |
12 | resources :line_items, only: :create
13 |
14 | resources :option_types, only: [:index, :show] do
15 | resources :option_values, :products, only: [:index, :show]
16 | end
17 |
18 | resources :option_values, only: [:index, :show] do
19 | resource :option_type, only: :show
20 | resources :variants, only: [:index, :show]
21 | end
22 |
23 | resources :orders, only: [:index, :show]
24 |
25 | resources :prices, only: [:index, :show] do
26 | resource :variant, :product, only: :show
27 | end
28 |
29 | resources :products, only: [:index, :show] do
30 | resources :option_types, :variants, :images, only: [:index, :show]
31 | end
32 |
33 | resources :states, only: [:index, :show] do
34 | resource :country, only: :show
35 | end
36 |
37 | resources :taxonomies, only: [:index, :show] do
38 | resources :taxons, only: [:index, :show]
39 | end
40 |
41 | resources :taxons, only: [:index, :show] do
42 | resources :children, :products, only: [:index, :show]
43 | resource :parent, only: :show, controller: 'taxons'
44 | resource :taxonomy, only: :show
45 | end
46 |
47 | resources :variants, only: [:index, :show] do
48 | resources :prices, :option_values, :images, only: [:index, :show]
49 | resource :product, only: :show
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/prices_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::PricesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:price) { create :price }
5 | let(:new_variant) { create :variant }
6 |
7 | describe '#index' do
8 | it 'will list all prices' do
9 | get :index
10 | price_ids = parse_json(response.body)['data'].map do |price|
11 | price['id']
12 | end
13 | expect(price_ids).to include price.id.to_s
14 | end
15 |
16 | context 'by variant id' do
17 | it 'will list its prices' do
18 | get :index, variant_id: price.variant.id
19 | price_ids = parse_json(response.body)['data'].map do |price|
20 | price['id']
21 | end
22 | expect(price_ids).to include price.id.to_s
23 | end
24 |
25 | it 'will not list another variants prices' do
26 | get :index, variant_id: new_variant.id
27 | price_ids = parse_json(response.body)['data'].map do |price|
28 | price['id']
29 | end
30 | expect(price_ids).to_not include price.id.to_s
31 | end
32 | end
33 | end
34 |
35 | describe '#show' do
36 | it 'will show the price' do
37 | get :show, id: price.id
38 | price_id = parse_json(response.body)['data']['id']
39 | expect(price_id).to eql price.id.to_s
40 | end
41 |
42 | context 'by variant id' do
43 | it 'will show its prices' do
44 | get :show, id: price.id, variant_id: price.variant.id
45 | price_id = parse_json(response.body)['data']['id']
46 | expect(price_id).to eql price.id.to_s
47 | end
48 |
49 | it 'will not show another variants price' do
50 | get :show, id: price.id, variant_id: new_variant.id
51 | expect(response).to have_http_status 404
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/taxons_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::TaxonsController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:taxon) { create :taxon }
5 |
6 | describe '#index' do
7 | it 'will list the taxons' do
8 | get :index
9 | taxon_ids = parse_json(response.body)['data'].map do |taxon|
10 | taxon['id']
11 | end
12 | expect(taxon_ids).to include taxon.id.to_s
13 | end
14 |
15 | it 'can list the taxons filtered by taxonomy_id' do
16 | get :index, taxonomy_id: taxon.taxonomy.id
17 | taxon_ids = parse_json(response.body)['data'].map do |taxon|
18 | taxon['id']
19 | end
20 | expect(taxon_ids).to include taxon.id.to_s
21 | end
22 | end
23 |
24 | describe '#show' do
25 | it 'will show the selected taxon' do
26 | get :show, id: taxon.id
27 | taxon_id = parse_json(response.body)['data']['id']
28 | expect(taxon_id).to eql taxon.id.to_s
29 | end
30 |
31 | context 'by parent' do
32 | it 'can show a taxons parent' do
33 | child = create :taxon, parent: taxon
34 | get :show, taxon_id: child.id
35 | taxon_id = parse_json(response.body)['data']['id']
36 | expect(taxon_id).to eql taxon.id.to_s
37 | end
38 | end
39 |
40 | context 'by taxonomy_id' do
41 | it 'will fetch the selected taxon' do
42 | get :show, taxonomy_id: taxon.taxonomy.id, id: taxon.id
43 | taxon_id = parse_json(response.body)['data']['id']
44 | expect(taxon_id).to eql taxon.id.to_s
45 | end
46 |
47 | it 'when taxon does not belong to taxonomy' do
48 | new_taxonomy = create :taxonomy
49 | get :show, taxonomy_id: new_taxonomy.id, id: taxon.id
50 | expect(response).to have_http_status 404
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/docs/source/includes/_states.md:
--------------------------------------------------------------------------------
1 | # States
2 |
3 | ## List States
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/states"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_states",
15 | "attributes": {
16 | "name": "Canillo",
17 | "abbr": "02"
18 | },
19 | "relationships": {
20 | "country": {
21 | "data": {
22 | "type": "spree_countries",
23 | "id": "1"
24 | }
25 | }
26 | }
27 | }
28 | ]
29 | }
30 | ```
31 |
32 | This will list all of the states.
33 |
34 | ## Show State
35 |
36 | ```shell
37 | curl "https://example.com/api/v2/states/1"
38 | ```
39 |
40 | ```json
41 | {
42 | "data": {
43 | "id": "1",
44 | "type": "spree_states",
45 | "attributes": {
46 | "name": "Canillo",
47 | "abbr": "02"
48 | },
49 | "relationships": {
50 | "country": {
51 | "data": {
52 | "type": "spree_countries",
53 | "id": "1"
54 | }
55 | }
56 | }
57 | }
58 | }
59 | ```
60 |
61 | Select a state via its `id`.
62 |
63 | ## Show Country of a States
64 |
65 | ```shell
66 | curl "https://example.com/api/v2/states/1/countries"
67 | ```
68 |
69 | ```json
70 | {
71 | "data": {
72 | "id": "1",
73 | "type": "spree_countries",
74 | "attributes": {
75 | "iso_name": "ANDORRA",
76 | "iso": "AD",
77 | "iso3": "AND",
78 | "name": "Andorra",
79 | "numcode": 20,
80 | "states_required": true
81 | },
82 | "relationships": {
83 | "states": {
84 | "data": [
85 | {
86 | "type": "spree_states",
87 | "id": "1"
88 | }
89 | ]
90 | }
91 | }
92 | }
93 | }
94 | ```
95 |
96 | View the country that a state belongs to via the state's `id`.
97 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/orders_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::OrdersController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let(:order) { create :order }
5 | let(:user) { order.user }
6 | let(:admin) { create :admin_user }
7 |
8 | before { user.generate_spree_api_key! }
9 |
10 | describe '#index' do
11 | context 'when admin' do
12 | before { admin.generate_spree_api_key! }
13 |
14 | it 'will list all orders' do
15 | get :index, token: admin.spree_api_key
16 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i }
17 | expect(ids).to include order.id
18 | end
19 | end
20 |
21 | context 'when user' do
22 | let!(:new_order) { create :order }
23 |
24 | before { get :index, token: user.spree_api_key }
25 |
26 | it 'will not list another users order' do
27 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i }
28 | expect(ids).to_not include new_order.id
29 | end
30 |
31 | it 'will list the current users orders' do
32 | ids = JSON.parse(response.body)['data'].map { |d| d['id'].to_i }
33 | expect(ids).to include order.id
34 | end
35 | end
36 | end
37 |
38 | describe '#show' do
39 | context 'when admin' do
40 | before { admin.generate_spree_api_key! }
41 |
42 | it 'can respond with another users order' do
43 | get :show, token: admin.spree_api_key, id: order.id
44 | expect(JSON.parse(response.body)['data']['type']).to eql 'spree_orders'
45 | end
46 | end
47 |
48 | context 'when user' do
49 | it 'will not respond with another users order' do
50 | new_order = create :order
51 | get :show, token: user.spree_api_key, id: new_order.id
52 | expect(response).to have_http_status 404
53 | end
54 |
55 | it 'will respond with the current users order' do
56 | get :show, token: user.spree_api_key, id: order.id
57 | expect(JSON.parse(response.body)['data']['id'].to_i).to eql order.id
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/docs/source/javascripts/app/_search.js:
--------------------------------------------------------------------------------
1 | //= require ../lib/_lunr
2 | //= require ../lib/_jquery.highlight
3 | (function () {
4 | 'use strict';
5 |
6 | var content, searchResults;
7 | var highlightOpts = { element: 'span', className: 'search-highlight' };
8 |
9 | var index = new lunr.Index();
10 |
11 | index.ref('id');
12 | index.field('title', { boost: 10 });
13 | index.field('body');
14 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter);
15 |
16 | $(populate);
17 | $(bind);
18 |
19 | function populate() {
20 | $('h1, h2').each(function() {
21 | var title = $(this);
22 | var body = title.nextUntil('h1, h2');
23 | index.add({
24 | id: title.prop('id'),
25 | title: title.text(),
26 | body: body.text()
27 | });
28 | });
29 | }
30 |
31 | function bind() {
32 | content = $('.content');
33 | searchResults = $('.search-results');
34 |
35 | $('#input-search').on('keyup', search);
36 | }
37 |
38 | function search(event) {
39 | unhighlight();
40 | searchResults.addClass('visible');
41 |
42 | // ESC clears the field
43 | if (event.keyCode === 27) this.value = '';
44 |
45 | if (this.value) {
46 | var results = index.search(this.value).filter(function(r) {
47 | return r.score > 0.0001;
48 | });
49 |
50 | if (results.length) {
51 | searchResults.empty();
52 | $.each(results, function (index, result) {
53 | var elem = document.getElementById(result.ref);
54 | searchResults.append("
" + $(elem).text() + "");
55 | });
56 | highlight.call(this);
57 | } else {
58 | searchResults.html('');
59 | $('.search-results li').text('No Results Found for "' + this.value + '"');
60 | }
61 | } else {
62 | unhighlight();
63 | searchResults.removeClass('visible');
64 | }
65 | }
66 |
67 | function highlight() {
68 | if (this.value) content.highlight(this.value, highlightOpts);
69 | }
70 |
71 | function unhighlight() {
72 | content.unhighlight(highlightOpts);
73 | }
74 | })();
75 |
--------------------------------------------------------------------------------
/app/controllers/concerns/spree/api/v2/renderable.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Api
3 | module V2
4 | module Renderable
5 | extend ActiveSupport::Concern
6 |
7 | protected
8 |
9 | def render_collection(collection = [])
10 | collection = collection.where(filter_params).paginate(page_params)
11 | render json: collection, include: params[:include],
12 | meta: { page: page_details(collection) }
13 | end
14 |
15 | def render_instance(object = {})
16 | render json: object, include: params[:include]
17 | end
18 |
19 | def filter_params
20 | params.fetch(:filter, {}).permit(*filter_attributes).transform_values do |value|
21 | value.respond_to?(:each) ? value : value.split(',')
22 | end
23 | end
24 |
25 | private
26 |
27 | def render_error(resource, options = {})
28 | response.status = options.delete(:status) || 400
29 | options[:response] = response
30 | render json: error_response(resource, options)
31 | end
32 |
33 | def error_response(resource, options = {})
34 | Spree::ErrorSerializer.new(resource, options).as_json
35 | end
36 |
37 | def filter_attributes
38 | attributes = serializer_attributes
39 | attributes << :id unless attributes.include?(:id)
40 | attributes.map { |a| [{ a => [] }, a] }.flatten
41 | end
42 |
43 | def page_details(collection)
44 | {
45 | total_items: collection.total_count,
46 | total_pages: collection.total_pages,
47 | number: (page_params[:number] || 1).to_i,
48 | size: (page_params[:size] || Kaminari.config.default_per_page).to_i
49 | }
50 | end
51 |
52 | def page_params
53 | params.fetch(:page, {}).permit(:number, :size)
54 | end
55 |
56 | def serializer_attributes
57 | serializer = "Spree::#{controller_name.camelize.singularize}Serializer"
58 | serializer.constantize._attributes
59 | end
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/spec/serializers/spree/order_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::OrderSerializer do
2 | let(:order) { create :completed_order_with_totals }
3 | subject { ActiveModel::Serializer::Adapter::JsonApi.new(described_class.new order) }
4 |
5 | its(:to_json) do
6 | is_expected.to be_json_eql <<-JSON
7 | {
8 | "data" : {
9 | "attributes" : {
10 | "additional_tax_total" : "0.0",
11 | "adjustment_total" : "0.0",
12 | "approved_at" : null,
13 | "canceled_at" : null,
14 | "channel" : "spree",
15 | "completed_at" : "#{order.completed_at.iso8601(3)}",
16 | "confirmation_delivered" : false,
17 | "currency" : "USD",
18 | "display_additional_tax_total" : "$0.00",
19 | "display_adjustment_total" : "$0.00",
20 | "display_included_tax_total" : "$0.00",
21 | "display_item_total" : "$10.00",
22 | "display_promo_total" : "$0.00",
23 | "display_shipment_total" : "$100.00",
24 | "display_total" : "$110.00",
25 | "email" : "#{order.email}",
26 | "included_tax_total" : "0.0",
27 | "item_count" : 1,
28 | "item_total" : "10.0",
29 | "number" : "#{order.number}",
30 | "payment_state" : null,
31 | "payment_total" : "0.0",
32 | "promo_total" : "0.0",
33 | "shipment_state" : null,
34 | "shipment_total" : "100.0",
35 | "special_instructions" : null,
36 | "state" : "complete",
37 | "total" : "110.0"
38 | },
39 | "relationships" : {
40 | "bill_address" : {
41 | "data" : {
42 | "type" : "spree_addresses"
43 | }
44 | },
45 | "line_items" : {
46 | "data" : [
47 | {
48 | "type" : "spree_line_items"
49 | }
50 | ]
51 | },
52 | "ship_address" : {
53 | "data" : {
54 | "type" : "spree_addresses"
55 | }
56 | },
57 | "user" : {
58 | "data" : {
59 | "type" : "spree_users"
60 | }
61 | }
62 | },
63 | "type" : "spree_orders"
64 | }
65 | }
66 | JSON
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/option_types_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::OptionTypesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:option_type) { create :option_type }
5 | let(:product) { create :product, option_types: [option_type] }
6 | let(:new_product) { create :product }
7 |
8 | describe '#index' do
9 | it 'will list all option types' do
10 | get :index
11 | option_type_ids = parse_json(response.body)['data'].map do |option_type|
12 | option_type['id']
13 | end
14 | expect(option_type_ids).to include option_type.id.to_s
15 | end
16 |
17 | context 'by product' do
18 | it 'will list option types' do
19 | get :index, product_id: product.id
20 | option_type_ids = parse_json(response.body)['data'].map do |option_type|
21 | option_type['id']
22 | end
23 | expect(option_type_ids).to include option_type.id.to_s
24 | end
25 |
26 | it 'will not list option types belonging to a different product' do
27 | get :index, product_id: new_product.id
28 | data = parse_json(response.body)['data']
29 | expect(data).to be_empty
30 | end
31 | end
32 | end
33 |
34 | describe '#show' do
35 | it 'will find selected option_type by id' do
36 | get :show, id: option_type.id
37 | option_type_id = parse_json(response.body)['data']['id']
38 | expect(option_type_id).to eql option_type.id.to_s
39 | end
40 |
41 | context 'by product' do
42 | it 'will list option types' do
43 | get :show, id: option_type.id, product_id: product.id
44 | option_type_id = parse_json(response.body)['data']['id']
45 | expect(option_type_id).to eql option_type.id.to_s
46 | end
47 |
48 | it 'will not list option types belonging to a different product' do
49 | get :show, id: option_type.id, product_id: new_product.id
50 | expect(response).to have_http_status 404
51 | end
52 | end
53 |
54 | context 'by option value' do
55 | let(:option_value) { create :option_value, option_type: option_type }
56 | let(:new_option_value) { create :option_value }
57 |
58 | it 'will list option types' do
59 | get :show, option_value_id: option_value.id
60 | option_type_id = parse_json(response.body)['data']['id']
61 | expect(option_type_id).to eql option_type.id.to_s
62 | end
63 |
64 | it 'will not list option types belonging to a different option value' do
65 | get :show, option_value_id: new_option_value.id
66 | option_type_id = parse_json(response.body)['data']['id']
67 | expect(option_type_id).to_not eql option_type.id.to_s
68 | end
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/docs/source/includes/_countries.md:
--------------------------------------------------------------------------------
1 | # Countries
2 |
3 | ## List Countries
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/countries"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_countries",
15 | "attributes": {
16 | "iso_name": "ANDORRA",
17 | "iso": "AD",
18 | "iso3": "AND",
19 | "name": "Andorra",
20 | "numcode": 20,
21 | "states_required": true
22 | },
23 | "relationships": {
24 | "states": {
25 | "data": [
26 | {
27 | "type": "spree_states",
28 | "id": "1"
29 | }
30 | ]
31 | }
32 | }
33 | }
34 | ]
35 | }
36 | ```
37 |
38 | List all of the ~200 countries in the DB.
39 |
40 | ## Show Country
41 |
42 | ```shell
43 | curl "https://example.com/api/v2/countries/1"
44 | ```
45 |
46 | ```json
47 | {
48 | "data": {
49 | "id": "1",
50 | "type": "spree_countries",
51 | "attributes": {
52 | "iso_name": "ANDORRA",
53 | "iso": "AD",
54 | "iso3": "AND",
55 | "name": "Andorra",
56 | "numcode": 20,
57 | "states_required": true
58 | },
59 | "relationships": {
60 | "states": {
61 | "data": [
62 | {
63 | "type": "spree_states",
64 | "id": "1"
65 | }
66 | ]
67 | }
68 | }
69 | }
70 | }
71 | ```
72 |
73 | Select a country via its `id`.
74 |
75 | ## List States of a Country
76 |
77 | ```shell
78 | curl "https://example.com/api/v2/countries/1/states"
79 | ```
80 |
81 | ```json
82 | {
83 | "data": [
84 | {
85 | "id": "1",
86 | "type": "spree_states",
87 | "attributes": {
88 | "name": "Canillo",
89 | "abbr": "02"
90 | },
91 | "relationships": {
92 | "country": {
93 | "data": {
94 | "type": "spree_countries",
95 | "id": "1"
96 | }
97 | }
98 | }
99 | }
100 | ]
101 | }
102 | ```
103 |
104 | See all of the states that a country owns via the country's `id`.
105 |
106 | ## Show State of a Country
107 |
108 | ```shell
109 | curl "https://example.com/api/v2/countries/1/states/1"
110 | ```
111 |
112 | ```json
113 | {
114 | "data": {
115 | "id": "1",
116 | "type": "spree_states",
117 | "attributes": {
118 | "name": "Canillo",
119 | "abbr": "02"
120 | },
121 | "relationships": {
122 | "country": {
123 | "data": {
124 | "type": "spree_countries",
125 | "id": "1"
126 | }
127 | }
128 | }
129 | }
130 | }
131 | ```
132 |
133 | View one of the states that a country owns via the country's `id` and the state's `id`.
134 |
--------------------------------------------------------------------------------
/docs/source/fonts/slate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/images_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::ImagesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:image) { create :image }
5 |
6 | let(:product) { create :product }
7 | let(:new_product) { create :product }
8 |
9 | let(:variant) { create :variant, images: [image] }
10 | let(:new_variant) { create :variant, images: [create(:image)] }
11 |
12 | describe '#index' do
13 | it 'will list the images' do
14 | get :index
15 | image_ids = parse_json(response.body)['data'].map do |image|
16 | image['id']
17 | end
18 | expect(image_ids).to include image.id.to_s
19 | end
20 |
21 | context 'by product id' do
22 | it 'will list its images' do
23 | product.images << image
24 | get :index, product_id: product.id
25 | image_ids = parse_json(response.body)['data'].map do |image|
26 | image['id']
27 | end
28 | expect(image_ids).to include image.id.to_s
29 | end
30 |
31 | it 'will not list another products images' do
32 | get :index, product_id: new_product.id
33 | data = parse_json(response.body)['data']
34 | expect(data).to be_empty
35 | end
36 | end
37 |
38 | context 'by variant id' do
39 | it 'will list its images' do
40 | get :index, variant_id: variant.id
41 | image_ids = parse_json(response.body)['data'].map do |image|
42 | image['id']
43 | end
44 | expect(image_ids).to include image.id.to_s
45 | end
46 |
47 | it 'will not list another variants images' do
48 | get :index, variant_id: new_variant.id
49 | image_ids = parse_json(response.body)['data'].map do |image|
50 | image['id']
51 | end
52 | expect(image_ids).to_not include image.id.to_s
53 | end
54 | end
55 | end
56 |
57 | describe '#show' do
58 | it 'will show the image' do
59 | get :show, id: image.id
60 | image_id = parse_json(response.body)['data']['id']
61 | expect(image_id).to eql image.id.to_s
62 | end
63 |
64 | context 'by product id' do
65 | it 'will show its image' do
66 | product.images << image
67 | get :show, id: image.id, product_id: product.id
68 | image_id = parse_json(response.body)['data']['id']
69 | expect(image_id).to eql image.id.to_s
70 | end
71 |
72 | it 'will not show another products image' do
73 | get :show, id: image.id, product_id: new_product.id
74 | expect(response).to have_http_status 404
75 | end
76 | end
77 |
78 | context 'by variant id' do
79 | it 'will show its image' do
80 | get :show, id: image.id, variant_id: variant.id
81 | image_id = parse_json(response.body)['data']['id']
82 | expect(image_id).to eql image.id.to_s
83 | end
84 |
85 | it 'will not show another products image' do
86 | get :show, id: image.id, variant_id: new_variant.id
87 | expect(response).to have_http_status 404
88 | end
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/docs/source/stylesheets/print.css.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | @import 'normalize';
3 | @import 'compass';
4 | @import 'variables';
5 | @import 'icon-font';
6 |
7 | /*
8 | Copyright 2008-2013 Concur Technologies, Inc.
9 |
10 | Licensed under the Apache License, Version 2.0 (the "License"); you may
11 | not use this file except in compliance with the License. You may obtain
12 | a copy of the License at
13 |
14 | http://www.apache.org/licenses/LICENSE-2.0
15 |
16 | Unless required by applicable law or agreed to in writing, software
17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
19 | License for the specific language governing permissions and limitations
20 | under the License.
21 | */
22 |
23 | $print-color: #999;
24 | $print-color-light: #ccc;
25 | $print-font-size: 12px;
26 |
27 | body {
28 | @extend %default-font;
29 | }
30 |
31 | .tocify, .toc-footer, .lang-selector, .search, #nav-button {
32 | display: none;
33 | }
34 |
35 | .tocify-wrapper>img {
36 | margin: 0 auto;
37 | display: block;
38 | }
39 |
40 | .content {
41 | font-size: 12px;
42 |
43 | pre, code {
44 | @extend %code-font;
45 | @extend %break-words;
46 | border: 1px solid $print-color;
47 | border-radius: 5px;
48 | font-size: 0.8em;
49 | }
50 |
51 | pre {
52 | padding: 1.3em;
53 | }
54 |
55 | code {
56 | padding: 0.2em;
57 | }
58 |
59 | table {
60 | border: 1px solid $print-color;
61 | tr {
62 | border-bottom: 1px solid $print-color;
63 | }
64 | td,th {
65 | padding: 0.7em;
66 | }
67 | }
68 |
69 | p {
70 | line-height: 1.5;
71 | }
72 |
73 | a {
74 | text-decoration: none;
75 | color: #000;
76 | }
77 |
78 | h1 {
79 | @extend %header-font;
80 | font-size: 2.5em;
81 | padding-top: 0.5em;
82 | padding-bottom: 0.5em;
83 | margin-top: 1em;
84 | margin-bottom: $h1-margin-bottom;
85 | border: 2px solid $print-color-light;
86 | border-width: 2px 0;
87 | text-align: center;
88 | }
89 |
90 | h2 {
91 | @extend %header-font;
92 | font-size: 1.8em;
93 | margin-top: 2em;
94 | border-top: 2px solid $print-color-light;
95 | padding-top: 0.8em;
96 | }
97 |
98 | h1+h2, h1+div+h2 {
99 | border-top: none;
100 | padding-top: 0;
101 | margin-top: 0;
102 | }
103 |
104 | h3, h4 {
105 | @extend %header-font;
106 | font-size: 0.8em;
107 | margin-top: 1.5em;
108 | margin-bottom: 0.8em;
109 | text-transform: uppercase;
110 | }
111 |
112 | h5, h6 {
113 | text-transform: uppercase;
114 | }
115 |
116 | aside {
117 | padding: 1em;
118 | border: 1px solid $print-color-light;
119 | border-radius: 5px;
120 | margin-top: 1.5em;
121 | margin-bottom: 1.5em;
122 | line-height: 1.6;
123 | }
124 |
125 | aside:before {
126 | vertical-align: middle;
127 | padding-right: 0.5em;
128 | font-size: 14px;
129 | }
130 |
131 | aside.notice:before {
132 | @extend %icon-info-sign;
133 | }
134 |
135 | aside.warning:before {
136 | @extend %icon-exclamation-sign;
137 | }
138 |
139 | aside.success:before {
140 | @extend %icon-ok-sign;
141 | }
142 | }
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/line_items_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::LineItemsController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let(:order) { create :order }
5 | let(:user) { order.user }
6 | let(:variant) { create :variant }
7 |
8 | before { user.generate_spree_api_key! }
9 |
10 | describe '#create' do
11 | let(:line_item_data) do
12 | {
13 | attributes: {
14 | order_id: order.id,
15 | variant_id: variant.id,
16 | quantity: 1
17 | }
18 | }
19 | end
20 |
21 | context 'on success' do
22 | it 'will respond with line item' do
23 | post :create, token: user.spree_api_key, data: line_item_data
24 | expect(JSON.parse(response.body)['data']['type']).to eql 'spree_line_items'
25 | end
26 | end
27 |
28 | context 'when out of range' do
29 | before do
30 | line_item_data[:attributes][:quantity] = 123_123_123_123
31 | post :create, token: user.spree_api_key, data: line_item_data
32 | end
33 |
34 | it 'will respond with quantity too high error' do
35 | expect(response.body).to be_json_eql <<-JSON
36 | {
37 | "errors" : [
38 | {
39 | "code": "400",
40 | "detail" : #{Spree.t('api.errors.quantity_too_high.detail').gsub("\n", '').to_json},
41 | "meta" : {},
42 | "status": "Bad Request",
43 | "title" : #{Spree.t('api.errors.quantity_too_high.title').titleize.to_json}
44 | }
45 | ]
46 | }
47 | JSON
48 | end
49 | end
50 |
51 | context 'when record not found' do
52 | before do
53 | line_item_data[:attributes][:order_id] = 0
54 | post :create, token: user.spree_api_key, data: line_item_data
55 | end
56 |
57 | it 'will respond with record not found error' do
58 | expect(response.body).to be_json_eql <<-JSON
59 | {
60 | "errors" : [
61 | {
62 | "code": "400",
63 | "detail" : #{Spree.t('api.errors.record_not_found.detail').gsub("\n", '').to_json},
64 | "meta" : {},
65 | "status": "Bad Request",
66 | "title" : #{Spree.t('api.errors.record_not_found.title').to_json}
67 | }
68 | ]
69 | }
70 | JSON
71 | end
72 | end
73 |
74 | context 'when record is invalid' do
75 | let(:stock_item) { variant.stock_items.first }
76 |
77 | before do
78 | quantity = stock_item.count_on_hand + 1
79 | stock_item.update(backorderable: false)
80 | line_item_data[:attributes][:quantity] = quantity
81 | post :create, token: user.spree_api_key, data: line_item_data
82 | end
83 |
84 | it 'will respond with product out of stock' do
85 | expect(response.body).to be_json_eql <<-JSON
86 | {
87 | "errors" : [
88 | {
89 | "code": "400",
90 | "detail" : "#{Spree.t('api.errors.product_out_of_stock.detail').gsub("\n", '')}",
91 | "meta" : {},
92 | "status": "Bad Request",
93 | "title" : #{Spree.t('api.errors.product_out_of_stock.title').titleize.to_json}
94 | }
95 | ]
96 | }
97 | JSON
98 | end
99 | end
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/option_values_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::OptionValuesController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:option_value) { create :option_value }
5 | let(:new_option_value) { create :option_value }
6 | let(:variant) { create :variant, option_values: [option_value] }
7 | let(:new_variant) { create :variant }
8 |
9 | describe '#index' do
10 | it 'will list all option values' do
11 | get :index
12 | option_value_ids = parse_json(response.body)['data'].map do |option_value|
13 | option_value['id']
14 | end
15 | expect(option_value_ids).to include option_value.id.to_s
16 | end
17 |
18 | context 'by option type' do
19 | it 'will list all option values' do
20 | get :index, option_type_id: option_value.option_type.id
21 | option_value_ids = parse_json(response.body)['data'].map do |option_value|
22 | option_value['id']
23 | end
24 | expect(option_value_ids).to include option_value.id.to_s
25 | end
26 |
27 | it 'will not list option value belonging to a different option type' do
28 | get :index, option_type_id: new_option_value.option_type.id
29 | option_value_ids = parse_json(response.body)['data'].map do |option_value|
30 | option_value['id']
31 | end
32 | expect(option_value_ids).to_not include option_value.id.to_s
33 | end
34 | end
35 |
36 | context 'by variant' do
37 | it 'will list all option values' do
38 | get :index, variant_id: variant.id
39 | option_value_ids = parse_json(response.body)['data'].map do |option_value|
40 | option_value['id']
41 | end
42 | expect(option_value_ids).to include option_value.id.to_s
43 | end
44 |
45 | it 'will not list option value belonging to a different variant' do
46 | get :index, variant_id: new_variant.id
47 | option_value_ids = parse_json(response.body)['data'].map do |option_value|
48 | option_value['id']
49 | end
50 | expect(option_value_ids).to_not include option_value.id.to_s
51 | end
52 | end
53 | end
54 |
55 | describe '#show' do
56 | it 'will find selected option value by id' do
57 | get :show, id: option_value.id
58 | option_value_id = parse_json(response.body)['data']['id']
59 | expect(option_value_id).to eql option_value.id.to_s
60 | end
61 |
62 | context 'by option type' do
63 | it 'will find option value by option type' do
64 | get :show, option_type_id: option_value.option_type.id, id: option_value.id
65 | option_value_id = parse_json(response.body)['data']['id']
66 | expect(option_value_id).to eql option_value.id.to_s
67 | end
68 |
69 | it 'will not find option value belonging to a different option type' do
70 | get :show, option_type_id: new_option_value.option_type.id, id: option_value.id
71 | expect(response).to have_http_status 404
72 | end
73 | end
74 |
75 | context 'by variant' do
76 | it 'will find option value by option type' do
77 | get :show, variant_id: variant.id, id: option_value.id
78 | option_value_id = parse_json(response.body)['data']['id']
79 | expect(option_value_id).to eql option_value.id.to_s
80 | end
81 |
82 | it 'will not find option value belonging to a different option type' do
83 | get :show, variant_id: new_variant.id, id: option_value.id
84 | expect(response).to have_http_status 404
85 | end
86 | end
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/products_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::ProductsController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:product) { create :product }
5 | let(:taxon) { create :taxon, products: [product] }
6 | let(:new_taxon) { create :taxon }
7 |
8 | describe '#index' do
9 | it 'will respond with products' do
10 | get :index
11 | expect(JSON.parse(response.body)['data'].size).to eql 1
12 | end
13 |
14 | context 'by taxon id' do
15 | it 'will respond with the taxons products' do
16 | get :index, taxon_id: taxon.id
17 | product_ids = parse_json(response.body)['data'].map do |products|
18 | products['id']
19 | end
20 | expect(product_ids).to include product.id.to_s
21 | end
22 |
23 | it 'will not respond with a product that is not associated to the taxon' do
24 | get :index, taxon_id: new_taxon.id
25 | data = parse_json(response.body)['data']
26 | expect(data).to be_empty
27 | end
28 | end
29 | end
30 |
31 | describe '#show' do
32 | it 'will render the selected product' do
33 | get :show, id: product.id
34 | expect(parse_json(response.body)['data']['id']).to eql product.id.to_s
35 | end
36 |
37 | it 'can find a product by slug' do
38 | get :show, id: product.slug
39 | product_id = parse_json(response.body)['data']['id']
40 | expect(product_id).to eql product.id.to_s
41 | end
42 |
43 | context 'by taxon id' do
44 | it 'will respond with the taxons products' do
45 | get :show, taxon_id: taxon.id, id: product.id
46 | product_id = parse_json(response.body)['data']['id']
47 | expect(product_id).to eql product.id.to_s
48 | end
49 |
50 | it 'will not respond with a product that is not associated to the taxon' do
51 | get :show, taxon_id: new_taxon.id, id: product.id
52 | expect(response).to have_http_status 404
53 | end
54 | end
55 |
56 | context 'by option type' do
57 | let(:option_type) { create :option_type, products: [product] }
58 | let(:new_option_type) { create :option_type }
59 |
60 | it 'will find product' do
61 | get :show, option_type_id: option_type.id, id: product.id
62 | product_id = parse_json(response.body)['data']['id']
63 | expect(product_id).to eql product.id.to_s
64 | end
65 |
66 | it 'will not find a product belong to a different option type' do
67 | get :show, option_type_id: new_option_type.id, id: product.id
68 | expect(response).to have_http_status 404
69 | end
70 | end
71 |
72 | context 'by variant id' do
73 | it 'will show its product' do
74 | get :show, variant_id: product.master.id
75 | product_id = parse_json(response.body)['data']['id']
76 | expect(product_id).to eql product.id.to_s
77 | end
78 | end
79 |
80 | context 'by price id' do
81 | it 'will show its product' do
82 | get :show, price_id: product.master.prices.first.id
83 | product_id = parse_json(response.body)['data']['id']
84 | expect(product_id).to eql product.id.to_s
85 | end
86 | end
87 |
88 | context 'by image id' do
89 | let(:image) { create :image }
90 |
91 | it 'will show its product' do
92 | product.images << image
93 | get :show, image_id: image.id
94 | product_id = parse_json(response.body)['data']['id']
95 | expect(product_id).to eql product.id.to_s
96 | end
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/spec/controllers/spree/api/v2/variants_controller_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::Api::V2::VariantsController do
2 | routes { Spree::Core::Engine.routes }
3 |
4 | let!(:variant) { create :variant }
5 | let(:new_option_value) { create :option_value }
6 |
7 | describe '#index' do
8 | it 'will list all variants' do
9 | get :index
10 | variant_ids = parse_json(response.body)['data'].map do |variant|
11 | variant['id']
12 | end
13 | expect(variant_ids).to include variant.id.to_s
14 | end
15 |
16 | context 'by product id' do
17 | let(:new_product) { create :product }
18 |
19 | it 'will list its variants' do
20 | get :index, product_id: variant.product.id
21 | variant_ids = parse_json(response.body)['data'].map do |variant|
22 | variant['id']
23 | end
24 | expect(variant_ids).to include variant.id.to_s
25 | end
26 |
27 | it 'will not list another products variants' do
28 | get :index, product_id: new_product.id
29 | variant_ids = parse_json(response.body)['data'].map do |variant|
30 | variant['id']
31 | end
32 | expect(variant_ids).to_not include variant.id.to_s
33 | end
34 | end
35 |
36 | context 'by option value' do
37 | it 'will list its variants' do
38 | get :index, option_value_id: variant.option_values.first.id
39 | variant_ids = parse_json(response.body)['data'].map do |variant|
40 | variant['id']
41 | end
42 | expect(variant_ids).to include variant.id.to_s
43 | end
44 |
45 | it 'will not list another products variant' do
46 | get :index, option_value_id: new_option_value.id
47 | data = parse_json(response.body)['data']
48 | expect(data).to be_empty
49 | end
50 | end
51 | end
52 |
53 | describe '#show' do
54 | it 'will show the variant' do
55 | get :show, id: variant.id
56 | variant_id = parse_json(response.body)['data']['id']
57 | expect(variant_id).to eql variant.id.to_s
58 | end
59 |
60 | context 'by product id' do
61 | let(:new_product) { create :product }
62 |
63 | it 'will show its variant' do
64 | get :show, id: variant.id, product_id: variant.product.id
65 | variant_id = parse_json(response.body)['data']['id']
66 | expect(variant_id).to eql variant.id.to_s
67 | end
68 |
69 | it 'will not show another products variant' do
70 | get :show, id: variant.id, product_id: new_product.id
71 | expect(response).to have_http_status 404
72 | end
73 | end
74 |
75 | context 'by option_value id' do
76 | it 'will show its variant' do
77 | get :show, id: variant.id, option_value_id: variant.option_values.first.id
78 | variant_id = parse_json(response.body)['data']['id']
79 | expect(variant_id).to eql variant.id.to_s
80 | end
81 |
82 | it 'will not show another option_values variant' do
83 | get :show, id: variant.id, option_value_id: new_option_value.id
84 | expect(response).to have_http_status 404
85 | end
86 | end
87 |
88 | context 'by price id' do
89 | it 'will show its variant' do
90 | get :show, price_id: variant.prices.first.id
91 | variant_id = parse_json(response.body)['data']['id']
92 | expect(variant_id).to eql variant.id.to_s
93 | end
94 | end
95 |
96 | context 'by image id' do
97 | let(:image) { create :image, viewable: variant }
98 |
99 | it 'will show its variant' do
100 | get :show, image_id: image.id
101 | variant_id = parse_json(response.body)['data']['id']
102 | expect(variant_id).to eql variant.id.to_s
103 | end
104 | end
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/docs/source/includes/_line_items.md:
--------------------------------------------------------------------------------
1 | # Line Items
2 |
3 | ## Create Line Item
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/line_items"
7 | -X POST
8 | -d token=abc123
9 | -d data[attributes][order_id]=1
10 | -d data[attributes][variant_id]=1
11 | -d data[attributes][quantity]=1
12 | ```
13 |
14 | ```json
15 | {
16 | "data": {
17 | "attributes": {
18 | "additional_tax_total": "0",
19 | "adjustment_total": "0.0",
20 | "amount": "10.0",
21 | "cost_price": "17.0",
22 | "currency": "CAD",
23 | "display_amount": "$10.00 CAD",
24 | "display_price": "$10.00 CAD",
25 | "display_total": "$10.00 CAD",
26 | "order_id": 1,
27 | "price": "10.0",
28 | "quantity": 1,
29 | "total": "10.0",
30 | "variant_id": 1
31 | },
32 | "relationships": {
33 | "order": {
34 | "data": {
35 | "type": "spree_orders"
36 | }
37 | },
38 | "variant": {
39 | "data": {
40 | "type": "spree_variants"
41 | }
42 | }
43 | },
44 | "type": "spree_line_items"
45 | }
46 | }
47 | ```
48 |
49 | This endpoint allows you add a variant to a users order by creating a line item.
50 |
51 | ### When Out of Range
52 |
53 | > Quantity is out of range.
54 |
55 | ```shell
56 | curl "https://example.com/api/v2/line_items"
57 | -X POST
58 | -d token=abc123
59 | -d data[attributes][order_id]=1
60 | -d data[attributes][variant_id]=1
61 | -d data[attributes][quantity]=100000000000
62 | ```
63 |
64 | ```json
65 | {
66 | "errors": [
67 | {
68 | "code": "400",
69 | "detail": "Quantity is too High",
70 | "meta": {},
71 | "status": "Bad Request",
72 | "title": "The quantity that you have submitted is astronomically high, please tone it down a bit."
73 | }
74 | ]
75 | }
76 | ```
77 |
78 | When requesting an insanely large amount of variants to be added to your order, this will result in an error.
79 |
80 | ### When a Variant or an Order Could Not be Found
81 |
82 | > Order could not be found.
83 |
84 | ```shell
85 | curl "https://example.com/api/v2/line_items"
86 | -X POST
87 | -d token=abc123
88 | -d data[attributes][order_id]=0
89 | -d data[attributes][variant_id]=1
90 | -d data[attributes][quantity]=1
91 | ```
92 |
93 | ```json
94 | {
95 | "errors": [
96 | {
97 | "code": "400",
98 | "detail": "Record Not Found",
99 | "meta": {},
100 | "status": "Bad Request",
101 | "title": "One of the records that you were looking for could not be found. Please check to see if the record exists or if you're permitted to read it"
102 | }
103 | ]
104 | }
105 | ```
106 |
107 | Sometimes, you'll submit a bad variant id or order id.
108 | When this happens, you'll receive an error because of it.
109 |
110 | ### When the Product is Out of Stock
111 |
112 | > Product is out of stock.
113 |
114 | ```shell
115 | curl "https://example.com/api/v2/line_items"
116 | -X POST
117 | -d token=abc123
118 | -d data[attributes][order_id]=1
119 | -d data[attributes][variant_id]=1
120 | -d data[attributes][quantity]=1
121 | ```
122 |
123 | ```json
124 | {
125 | "errors": [
126 | {
127 | "code": "400",
128 | "detail": "Product is out of Stock",
129 | "meta": {},
130 | "status": "Bad Request",
131 | "title": "This product is out of stock for the selected quantity."
132 | }
133 | ]
134 | }
135 | ```
136 |
137 | Sometimes, there's just not going to be any left in stock.
138 | This can happen for both two reasons:
139 |
140 | 1. The variant is tracking inventory and its stock items `count_on_hand`s have all reached 0.
141 | 2. The variant is not backorderable.
142 |
143 | If both of these conditions are met, then you will reveice an out of stock error.
144 |
--------------------------------------------------------------------------------
/docs/source/stylesheets/_variables.scss:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | */
16 |
17 |
18 | ////////////////////////////////////////////////////////////////////////////////
19 | // CUSTOMIZE SLATE
20 | ////////////////////////////////////////////////////////////////////////////////
21 | // Use these settings to help adjust the appearance of Slate
22 |
23 |
24 | // BACKGROUND COLORS
25 | ////////////////////
26 | $nav-bg: #393939;
27 | $examples-bg: #393939;
28 | $code-bg: #292929;
29 | $code-annotation-bg: #1c1c1c;
30 | $nav-subitem-bg: #262626;
31 | $nav-active-bg: #2467af;
32 | $lang-select-border: #000;
33 | $lang-select-bg: #222;
34 | $lang-select-active-bg: $examples-bg; // feel free to change this to blue or something
35 | $lang-select-pressed-bg: #111; // color of language tab bg when mouse is pressed
36 | $main-bg: #eaf2f6;
37 | $aside-notice-bg: #8fbcd4;
38 | $aside-warning-bg: #c97a7e;
39 | $aside-success-bg: #6ac174;
40 | $search-notice-bg: #c97a7e;
41 |
42 |
43 | // TEXT COLORS
44 | ////////////////////
45 | $main-text: #333; // main content text color
46 | $nav-text: #fff;
47 | $nav-active-text: #fff;
48 | $lang-select-text: #fff; // color of unselected language tab text
49 | $lang-select-active-text: #fff; // color of selected language tab text
50 | $lang-select-pressed-text: #fff; // color of language tab text when mouse is pressed
51 |
52 |
53 | // SIZES
54 | ////////////////////
55 | $nav-width: 250px; // width of the navbar
56 | $examples-width: 50%; // portion of the screen taken up by code examples
57 | $logo-margin: 20px; // margin between nav items and logo, ignored if search is active
58 | $main-padding: 28px; // padding to left and right of content & examples
59 | $nav-padding: 15px; // padding to left and right of navbar
60 | $nav-v-padding: 10px; // padding used vertically around search boxes and results
61 | $nav-indent: 10px; // extra padding for ToC subitems
62 | $code-annotation-padding: 13px; // padding inside code annotations
63 | $h1-margin-bottom: 21px; // padding under the largest header tags
64 | $tablet-width: 930px; // min width before reverting to tablet size
65 | $phone-width: $tablet-width - $nav-width; // min width before reverting to mobile size
66 |
67 |
68 | // FONTS
69 | ////////////////////
70 | %default-font {
71 | font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei","微软雅黑", STXihei, "华文细黑", sans-serif;
72 | font-size: 13px;
73 | }
74 |
75 | %header-font {
76 | @extend %default-font;
77 | font-weight: bold;
78 | }
79 |
80 | %code-font {
81 | font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
82 | font-size: 12px;
83 | line-height: 1.5;
84 | }
85 |
86 |
87 | // OTHER
88 | ////////////////////
89 | $nav-active-shadow: #000;
90 | $nav-footer-border-color: #666;
91 | $nav-embossed-border-top: #000;
92 | $nav-embossed-border-bottom: #939393;
93 | $main-embossed-text-shadow: 0px 1px 0px #fff;
94 | $search-box-border-color: #666;
95 |
96 |
97 | ////////////////////////////////////////////////////////////////////////////////
98 | // INTERNAL
99 | ////////////////////////////////////////////////////////////////////////////////
100 | // These settings are probably best left alone.
101 |
102 | %break-words {
103 | word-break: break-all;
104 |
105 | /* Non standard for webkit */
106 | word-break: break-word;
107 |
108 | hyphens: auto;
109 | }
110 |
--------------------------------------------------------------------------------
/docs/source/includes/_prices.md:
--------------------------------------------------------------------------------
1 | # Prices
2 |
3 | ## List Prices
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/prices"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_prices",
15 | "attributes": {
16 | "amount": "15.99",
17 | "price": "15.99",
18 | "display_amount": "$15.99 CAD",
19 | "display_price": "$15.99 CAD",
20 | "currency": "CAD"
21 | },
22 | "relationships": {
23 | "variant": {
24 | "data": {
25 | "type": "spree_variants",
26 | "id": "1"
27 | }
28 | }
29 | }
30 | }
31 | ]
32 | }
33 | ```
34 |
35 | List all of the prices in the database.
36 |
37 | ## Show Price
38 |
39 | ```shell
40 | curl "https://example.com/api/v2/prices/1"
41 | ```
42 |
43 | ```json
44 | {
45 | "data": {
46 | "id": "1",
47 | "type": "spree_prices",
48 | "attributes": {
49 | "amount": "15.99",
50 | "price": "15.99",
51 | "display_amount": "$15.99 CAD",
52 | "display_price": "$15.99 CAD",
53 | "currency": "CAD"
54 | },
55 | "relationships": {
56 | "variant": {
57 | "data": {
58 | "type": "spree_variants",
59 | "id": "1"
60 | }
61 | }
62 | }
63 | }
64 | }
65 | ```
66 |
67 | Fetch a price via its `id`.
68 |
69 | ## Show Variant of a Price
70 |
71 | ```shell
72 | curl "https://example.com/api/v2/prices/1/variant"
73 | ```
74 |
75 | ```json
76 | {
77 | "data": {
78 | "id": "1",
79 | "type": "spree_variants",
80 | "attributes": {
81 | "sku": "ROR-00011",
82 | "weight": "0.0",
83 | "height": "1.0",
84 | "width": "3.0",
85 | "depth": "2.0",
86 | "is_master": true,
87 | "position": 1,
88 | "name": "Ruby on Rails Tote",
89 | "price": "15.99",
90 | "display_price" : "$15.99 CAD"
91 | },
92 | "relationships": {
93 | "prices": {
94 | "data": [
95 | {
96 | "type": "spree_prices",
97 | "id": "1"
98 | }
99 | ]
100 | },
101 | "option_values": {
102 | "data": [
103 | {
104 | "type": "spree_option_values",
105 | "id": "1"
106 | }
107 | ]
108 | },
109 | "images": {
110 | "data": [
111 | {
112 | "type": "spree_images",
113 | "id": "1"
114 | }
115 | ]
116 | },
117 | "product": {
118 | "data": {
119 | "type": "spree_products",
120 | "id": "1"
121 | }
122 | }
123 | }
124 | }
125 | }
126 | ```
127 |
128 | Fetch the variant a price belongs to via the price's `id`.
129 |
130 | ## Show Product of a Price
131 |
132 | ```shell
133 | curl "https://example.com/api/v2/prices/1/product"
134 | ```
135 |
136 | ```json
137 | {
138 | "data": {
139 | "id": "1",
140 | "type": "spree_products",
141 | "attributes": {
142 | "name": "Ruby on Rails Tote",
143 | "description": "Dolorem molestias sint maxime id at rem qui exercitationem. Neque voluptas corrupti magni suscipit iusto voluptatum. Ea quibusdam dolorem inventore praesentium sed dicta eveniet et. Rerum inventore laudantium quisquam earum consequatur dignissimos.",
144 | "slug": "ruby-on-rails-tote",
145 | "meta_description": null,
146 | "meta_keywords": null,
147 | "store_name": "Whole New Home"
148 | },
149 | "relationships": {
150 | "master": {
151 | "data": {
152 | "type": "spree_variants",
153 | "id": "1"
154 | }
155 | },
156 | "variants": {
157 | "data": []
158 | },
159 | "taxons": {
160 | "data": [
161 | {
162 | "type": "spree_taxons",
163 | "id": "18"
164 | }
165 | ]
166 | },
167 | "option_types": {
168 | "data": [
169 | {
170 | "type": "spree_option_types",
171 | "id": "1"
172 | }
173 | ]
174 | },
175 | "images": {
176 | "data": [
177 | {
178 | "type": "spree_images",
179 | "id": "1"
180 | }
181 | ]
182 | }
183 | }
184 | }
185 | }
186 | ```
187 |
188 | Fetch the product a price belongs to via the price's `id`.
189 |
--------------------------------------------------------------------------------
/docs/source/includes/_orders.md:
--------------------------------------------------------------------------------
1 | # Orders
2 |
3 | ## List Orders
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/orders"
7 | -d token=abc123
8 | ```
9 |
10 | ```json
11 | {
12 | "data" : [
13 | {
14 | "attributes" : {
15 | "additional_tax_total" : "0.0",
16 | "adjustment_total" : "0.0",
17 | "approved_at" : null,
18 | "canceled_at" : null,
19 | "channel" : "spree",
20 | "completed_at" : "2015-09-08T04:04:01.162Z",
21 | "confirmation_delivered" : false,
22 | "currency" : "CAD",
23 | "display_additional_tax_total" : "$0.00 CAD",
24 | "display_adjustment_total" : "$0.00 CAD",
25 | "display_included_tax_total" : "$0.00 CAD",
26 | "display_item_total" : "$10.00 CAD",
27 | "display_promo_total" : "$0.00 CAD",
28 | "display_shipment_total" : "$100.00 CAD",
29 | "display_total" : "$110.00 CAD",
30 | "email" : "spree@example.com",
31 | "included_tax_total" : "0.0",
32 | "item_count" : 0,
33 | "item_total" : "10.0",
34 | "number" : "#R123456789",
35 | "payment_state" : null,
36 | "payment_total" : "0.0",
37 | "promo_total" : "0.0",
38 | "shipment_state" : null,
39 | "shipment_total" : "100.0",
40 | "special_instructions" : null,
41 | "state" : "complete",
42 | "store_name" : "Example Store",
43 | "total" : "110.0"
44 | },
45 | "relationships" : {
46 | "bill_address" : {
47 | "data" : {
48 | "type" : "spree_addresses"
49 | }
50 | },
51 | "line_items" : {
52 | "data" : [
53 | {
54 | "type" : "spree_line_items"
55 | }
56 | ]
57 | },
58 | "ship_address" : {
59 | "data" : {
60 | "type" : "spree_addresses"
61 | }
62 | },
63 | "user" : {
64 | "data" : {
65 | "type" : "spree_users"
66 | }
67 | }
68 | },
69 | "type" : "spree_orders"
70 | }
71 | ]
72 | }
73 | ```
74 |
75 | List all of the orders in the DB if you're an admin.
76 | If you're a standard user, then only the orders that you own are listed.
77 |
78 | ## Show Order
79 |
80 | ```shell
81 | curl "https://example.com/api/v2/orders/:id"
82 | -d token=abc123
83 | ```
84 |
85 | ```json
86 | {
87 | "data" : {
88 | "attributes" : {
89 | "additional_tax_total" : "0.0",
90 | "adjustment_total" : "0.0",
91 | "approved_at" : null,
92 | "canceled_at" : null,
93 | "channel" : "spree",
94 | "completed_at" : "2015-09-08T04:04:01.162Z",
95 | "confirmation_delivered" : false,
96 | "currency" : "CAD",
97 | "display_additional_tax_total" : "$0.00 CAD",
98 | "display_adjustment_total" : "$0.00 CAD",
99 | "display_included_tax_total" : "$0.00 CAD",
100 | "display_item_total" : "$10.00 CAD",
101 | "display_promo_total" : "$0.00 CAD",
102 | "display_shipment_total" : "$100.00 CAD",
103 | "display_total" : "$110.00 CAD",
104 | "email" : "spree@example.com",
105 | "included_tax_total" : "0.0",
106 | "item_count" : 0,
107 | "item_total" : "10.0",
108 | "number" : "#R123456789",
109 | "payment_state" : null,
110 | "payment_total" : "0.0",
111 | "promo_total" : "0.0",
112 | "shipment_state" : null,
113 | "shipment_total" : "100.0",
114 | "special_instructions" : null,
115 | "state" : "complete",
116 | "store_name" : "Example Store",
117 | "total" : "110.0"
118 | },
119 | "relationships" : {
120 | "bill_address" : {
121 | "data" : {
122 | "type" : "spree_addresses"
123 | }
124 | },
125 | "line_items" : {
126 | "data" : [
127 | {
128 | "type" : "spree_line_items"
129 | }
130 | ]
131 | },
132 | "ship_address" : {
133 | "data" : {
134 | "type" : "spree_addresses"
135 | }
136 | },
137 | "user" : {
138 | "data" : {
139 | "type" : "spree_users"
140 | }
141 | }
142 | },
143 | "type" : "spree_orders"
144 | }
145 | }
146 | ```
147 |
148 | This will get an order via the id supplied.
149 | Please note that you can ony access the orders that you own.
150 | If you are an admin, you can view any order.
151 |
--------------------------------------------------------------------------------
/docs/source/javascripts/lib/_jquery.highlight.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Highlight plugin
3 | *
4 | * Based on highlight v3 by Johann Burkard
5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
6 | *
7 | * Code a little bit refactored and cleaned (in my humble opinion).
8 | * Most important changes:
9 | * - has an option to highlight only entire words (wordsOnly - false by default),
10 | * - has an option to be case sensitive (caseSensitive - false by default)
11 | * - highlight element tag and class names can be specified in options
12 | *
13 | * Usage:
14 | * // wrap every occurrance of text 'lorem' in content
15 | * // with (default options)
16 | * $('#content').highlight('lorem');
17 | *
18 | * // search for and highlight more terms at once
19 | * // so you can save some time on traversing DOM
20 | * $('#content').highlight(['lorem', 'ipsum']);
21 | * $('#content').highlight('lorem ipsum');
22 | *
23 | * // search only for entire word 'lorem'
24 | * $('#content').highlight('lorem', { wordsOnly: true });
25 | *
26 | * // don't ignore case during search of term 'lorem'
27 | * $('#content').highlight('lorem', { caseSensitive: true });
28 | *
29 | * // wrap every occurrance of term 'ipsum' in content
30 | * // with
31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' });
32 | *
33 | * // remove default highlight
34 | * $('#content').unhighlight();
35 | *
36 | * // remove custom highlight
37 | * $('#content').unhighlight({ element: 'em', className: 'important' });
38 | *
39 | *
40 | * Copyright (c) 2009 Bartek Szopka
41 | *
42 | * Licensed under MIT license.
43 | *
44 | */
45 |
46 | jQuery.extend({
47 | highlight: function (node, re, nodeName, className) {
48 | if (node.nodeType === 3) {
49 | var match = node.data.match(re);
50 | if (match) {
51 | var highlight = document.createElement(nodeName || 'span');
52 | highlight.className = className || 'highlight';
53 | var wordNode = node.splitText(match.index);
54 | wordNode.splitText(match[0].length);
55 | var wordClone = wordNode.cloneNode(true);
56 | highlight.appendChild(wordClone);
57 | wordNode.parentNode.replaceChild(highlight, wordNode);
58 | return 1; //skip added node in parent
59 | }
60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
63 | for (var i = 0; i < node.childNodes.length; i++) {
64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
65 | }
66 | }
67 | return 0;
68 | }
69 | });
70 |
71 | jQuery.fn.unhighlight = function (options) {
72 | var settings = { className: 'highlight', element: 'span' };
73 | jQuery.extend(settings, options);
74 |
75 | return this.find(settings.element + "." + settings.className).each(function () {
76 | var parent = this.parentNode;
77 | parent.replaceChild(this.firstChild, this);
78 | parent.normalize();
79 | }).end();
80 | };
81 |
82 | jQuery.fn.highlight = function (words, options) {
83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
84 | jQuery.extend(settings, options);
85 |
86 | if (words.constructor === String) {
87 | words = [words];
88 | }
89 | words = jQuery.grep(words, function(word, i){
90 | return word != '';
91 | });
92 | words = jQuery.map(words, function(word, i) {
93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
94 | });
95 | if (words.length == 0) { return this; };
96 |
97 | var flag = settings.caseSensitive ? "" : "i";
98 | var pattern = "(" + words.join("|") + ")";
99 | if (settings.wordsOnly) {
100 | pattern = "\\b" + pattern + "\\b";
101 | }
102 | var re = new RegExp(pattern, flag);
103 |
104 | return this.each(function () {
105 | jQuery.highlight(this, re, settings.element, settings.className);
106 | });
107 | };
108 |
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Solidus JSON API
2 |
3 | [](https://circleci.com/gh/wildcardlabs/solidus_json_api)
4 | [](https://coveralls.io/github/wildcardlabs/solidus_json_api?branch=master)
5 |
6 | A JSON API for Solidus.
7 |
8 | Documentation: [http://solidusapi.wildcardlabs.com](http://solidusapi.wildcardlabs.com)
9 |
10 | ## Installation
11 |
12 | Add solidus_json_api to your Gemfile:
13 |
14 | ```ruby
15 | gem 'solidus_json_api', github: 'wildcardlabs/solidus_json_api'
16 | ```
17 |
18 | ## Road to 1.0
19 |
20 | ***
21 | Please note that there actually has been a _lot_ of work done.
22 | The lack of checked off boxes doesn't give this gem the respect it deserves.
23 | ***
24 |
25 | There's several things blocking 1.0.
26 | Below, you'll see a bullet list of requirements that are both specific to the app, the JSON API spec, and third parties.
27 |
28 | - [ ] Manage permissions with CanCanCan.
29 | - [ ] Add support for XML.
30 | - [x] Add support for pagination.
31 | - [x] `page` keyword support for pagination.
32 | - [x] `links` document at the root for `self`, `next`, `prev`, `last`, and `first`.
33 | - [ ] Add support for filtration.
34 | - [x] Be able to filter onto the main data object.
35 | - [ ] Be able to filter the included relationships.
36 | - [ ] Provide JSON API specific error objects. Provide support for:
37 | - [ ] `id`
38 | - [ ] `links`
39 | - [ ] `about`
40 | - [x] `status`
41 | - [x] `code`
42 | - [x] `title`
43 | - [x] `detail`
44 | - [ ] `source`
45 | - [x] `pointer`
46 | - [ ] `parameter`
47 | - [x] `meta`
48 | - [ ] Only accept data in the JSON API format.
49 | - [ ] Respond with header `Content-Type: application/vnd.api+json`.
50 | - [ ] Add support for sparse fieldsets.
51 | - [ ] Add support for sorting.
52 |
53 | | Resource | index | create | show | update | destroy |
54 | |------------------|:-------:|:-------:|:-------:|:-------:|:-------:|
55 | | Adjustment | [ ] | [ ] | [ ] | [ ] | [ ] |
56 | | Address | [ ] | [ ] | [ ] | [ ] | [ ] |
57 | | Checkout | [ ] | [ ] | [ ] | [ ] | [ ] |
58 | | Country | [x] | [ ] | [x] | [ ] | [ ] |
59 | | Image | [x] | [ ] | [x] | [ ] | [ ] |
60 | | Line Item | [ ] | [x] | [ ] | [ ] | [ ] |
61 | | Option Type | [x] | [ ] | [x] | [ ] | [ ] |
62 | | Option Value | [x] | [ ] | [x] | [ ] | [ ] |
63 | | Order | [x] | [ ] | [x] | [ ] | [ ] |
64 | | Payment | [ ] | [ ] | [ ] | [ ] | [ ] |
65 | | Price | [x] | [ ] | [x] | [ ] | [ ] |
66 | | Product | [x] | [ ] | [x] | [ ] | [ ] |
67 | | Product Property | [ ] | [ ] | [ ] | [ ] | [ ] |
68 | | Return Auth | [ ] | [ ] | [ ] | [ ] | [ ] |
69 | | Shipment | [ ] | [ ] | [ ] | [ ] | [ ] |
70 | | State | [x] | [ ] | [x] | [ ] | [ ] |
71 | | Stock Location | [ ] | [ ] | [ ] | [ ] | [ ] |
72 | | Stock Movement | [ ] | [ ] | [ ] | [ ] | [ ] |
73 | | Stock Item | [ ] | [ ] | [ ] | [ ] | [ ] |
74 | | Store | [ ] | [ ] | [ ] | [ ] | [ ] |
75 | | Store Credit | [ ] | [ ] | [ ] | [ ] | [ ] |
76 | | Tax Category | [ ] | [ ] | [ ] | [ ] | [ ] |
77 | | Tax Rate | [ ] | [ ] | [ ] | [ ] | [ ] |
78 | | Taxon | [x] | [ ] | [x] | [ ] | [ ] |
79 | | Taxonomy | [x] | [ ] | [x] | [ ] | [ ] |
80 | | User | [x] | [ ] | [x] | [ ] | [ ] |
81 | | Variant | [x] | [ ] | [x] | [ ] | [ ] |
82 | | Zone | [ ] | [ ] | [ ] | [ ] | [ ] |
83 |
84 | ## Testing
85 |
86 | First bundle your dependencies, then run `rake`. `rake` will default to building the dummy app if it does not exist, then it will run the specs.
87 | The dummy app can be regenerated by using `rake test_app`.
88 |
89 | ```shell
90 | bundle
91 | bundle exec rake
92 | ```
93 |
94 | Copyright © 2015 Ben A. Morgan, released under the New BSD License
95 |
--------------------------------------------------------------------------------
/spec/serializers/spree/error_serializer_spec.rb:
--------------------------------------------------------------------------------
1 | describe Spree::ErrorSerializer do
2 | let(:address_errors) do
3 | [
4 | {
5 | "ship_address.firstname": ["can't be blank"],
6 | "ship_address.lastname": ["can't be blank"]
7 | },
8 | {
9 | "bill_address.firstname": ["can't be blank"],
10 | "bill_address.lastname": ["can't be blank"]
11 | }
12 | ]
13 | end
14 |
15 | subject { described_class }
16 |
17 | describe '#as_json' do
18 | before do
19 | @store = build(:store, code: nil, mail_from_address: nil)
20 | @store.save
21 | end
22 |
23 | it 'will format json to json api spec' do
24 | expect(described_class.new(@store).as_json).to be_json_eql <<-JSON
25 | {
26 | "errors": [
27 | {
28 | "title": "Code",
29 | "meta": {},
30 | "pointer": "data/attributes/code",
31 | "detail": "can't be blank"
32 | },
33 | {
34 | "title": "Mail From Address",
35 | "meta": {},
36 | "pointer": "data/attributes/mail_from_address",
37 | "detail": "can't be blank"
38 | }
39 | ]
40 | }
41 | JSON
42 | end
43 |
44 | it 'will format options as meta' do
45 | expect(described_class.new(@store, example: :content).as_json).to be_json_eql <<-JSON
46 | {
47 | "errors": [
48 | {
49 | "detail": "can't be blank",
50 | "meta": {
51 | "example": "content"
52 | },
53 | "pointer": "data/attributes/code",
54 | "title": "Code"
55 | },
56 | {
57 | "detail": "can't be blank",
58 | "meta": {
59 | "example": "content"
60 | },
61 | "pointer": "data/attributes/mail_from_address",
62 | "title": "Mail From Address"
63 | }
64 | ]
65 | }
66 | JSON
67 | end
68 |
69 | it 'will format a symbol' do
70 | expect(described_class.new(:invalid_token).as_json).to be_json_eql <<-JSON
71 | {
72 | "errors": [
73 | {
74 | "detail": "#{Spree.t('api.errors.invalid_token.detail')}",
75 | "meta": {},
76 | "title": "#{Spree.t('api.errors.invalid_token.title')}"
77 | }
78 | ]
79 | }
80 | JSON
81 | end
82 |
83 | it 'will format an array of errors' do
84 | expect(described_class.new(address_errors).as_json).to be_json_eql <<-JSON
85 | {
86 | "errors" : [
87 | {
88 | "detail" : "can't be blank",
89 | "meta" : {},
90 | "pointer" : "data/attributes/ship_address/firstname",
91 | "title" : "Ship Address Firstname"
92 | },
93 | {
94 | "detail" : "can't be blank",
95 | "meta" : {},
96 | "pointer" : "data/attributes/ship_address/lastname",
97 | "title" : "Ship Address Lastname"
98 | },
99 | {
100 | "detail" : "can't be blank",
101 | "meta" : {},
102 | "pointer" : "data/attributes/bill_address/firstname",
103 | "title" : "Bill Address Firstname"
104 | },
105 | {
106 | "detail" : "can't be blank",
107 | "meta" : {},
108 | "pointer" : "data/attributes/bill_address/lastname",
109 | "title" : "Bill Address Lastname"
110 | }
111 | ]
112 | }
113 | JSON
114 | end
115 |
116 | context 'can set a response when it is present' do
117 | let(:response) do
118 | Response ||= Struct.new(:status_message, :code)
119 | Response.new('OK', 200)
120 | end
121 |
122 | subject { described_class.new(:test, response: response).as_json }
123 |
124 | it do
125 | should be_json_eql <<-JSON
126 | {
127 | "errors" : [
128 | {
129 | "code" : 200,
130 | "detail" : #{Spree.t('api.errors.test.detail').to_json},
131 | "meta" : {},
132 | "status" : "OK",
133 | "title" : #{Spree.t('api.errors.test.title').titleize.to_json}
134 | }
135 | ]
136 | }
137 | JSON
138 | end
139 | end
140 | end
141 | end
142 |
--------------------------------------------------------------------------------
/docs/source/includes/_option_values.md:
--------------------------------------------------------------------------------
1 | # Option Values
2 |
3 | ## List Option Values
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/option_values"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_option_values",
15 | "attributes": {
16 | "name": "Small",
17 | "presentation": "S",
18 | "position": 1
19 | },
20 | "relationships": {
21 | "option_type": {
22 | "data": {
23 | "type": "spree_option_types",
24 | "id": "1"
25 | }
26 | }
27 | }
28 | }
29 | ]
30 | }
31 | ```
32 |
33 | List all of the option values.
34 |
35 | ## Show Option Value
36 |
37 | ```shell
38 | curl "https://example.com/api/v2/option_values/1"
39 | ```
40 |
41 | ```json
42 | {
43 | "data": {
44 | "id": "1",
45 | "type": "spree_option_values",
46 | "attributes": {
47 | "name": "Small",
48 | "presentation": "S",
49 | "position": 1
50 | },
51 | "relationships": {
52 | "option_type": {
53 | "data": {
54 | "type": "spree_option_types",
55 | "id": "1"
56 | }
57 | }
58 | }
59 | }
60 | }
61 | ```
62 |
63 | Find an option value by its `id`.
64 |
65 | ## Show Option Type of an Option Value
66 |
67 | ```shell
68 | curl "https://example.com/api/v2/option_values/1/option_type"
69 | ```
70 |
71 | ```json
72 | {
73 | "data": {
74 | "id": "1",
75 | "type": "spree_option_types",
76 | "attributes": {
77 | "name": "tshirt-size",
78 | "presentation": "Size",
79 | "position": 1
80 | },
81 | "relationships": {
82 | "option_values": {
83 | "data": [
84 | {
85 | "type": "spree_option_values",
86 | "id": "1"
87 | }
88 | ]
89 | },
90 | "products": {
91 | "data": [
92 | {
93 | "type": "spree_products",
94 | "id": "1"
95 | }
96 | ]
97 | }
98 | }
99 | }
100 | }
101 | ```
102 |
103 | Find the option type of an option value by the option value's `id`.
104 |
105 | ## List Variants of an Option Value
106 |
107 | ```shell
108 | curl "https://example.com/api/v2/option_values/1/variants"
109 | ```
110 |
111 | ```json
112 | {
113 | "data": [
114 | {
115 | "id": "1",
116 | "type": "spree_variants",
117 | "attributes": {
118 | "sku": "ROR-00001",
119 | "weight": "0.0",
120 | "height": null,
121 | "width": null,
122 | "depth": null,
123 | "is_master": false,
124 | "position": 2,
125 | "name": "Ruby on Rails Baseball Jersey",
126 | "price": "15.99",
127 | "display_price": "$15.99 CAD"
128 | },
129 | "relationships": {
130 | "prices": {
131 | "data": [
132 | {
133 | "type": "spree_prices",
134 | "id": "33"
135 | }
136 | ]
137 | },
138 | "option_values": {
139 | "data": [
140 | {
141 | "type": "spree_option_values",
142 | "id": "1"
143 | }
144 | ]
145 | },
146 | "images": {
147 | "data": [
148 | {
149 | "type": "spree_images",
150 | "id": "1"
151 | }
152 | ]
153 | },
154 | "product": {
155 | "data": {
156 | "type": "spree_products",
157 | "id": "3"
158 | }
159 | }
160 | }
161 | }
162 | ]
163 | }
164 | ```
165 |
166 | List all of the variants that belong to an option value via the option value's `id`.
167 |
168 | ## Show Variant of an Option Value
169 |
170 | ```shell
171 | curl "https://example.com/api/v2/option_values/1/variants/1"
172 | ```
173 |
174 | ```json
175 | {
176 | "data": {
177 | "id": "1",
178 | "type": "spree_variants",
179 | "attributes": {
180 | "sku": "ROR-00001",
181 | "weight": "0.0",
182 | "height": null,
183 | "width": null,
184 | "depth": null,
185 | "is_master": false,
186 | "position": 2,
187 | "name": "Ruby on Rails Baseball Jersey",
188 | "price": "15.99",
189 | "display_price": "$15.99 CAD"
190 | },
191 | "relationships": {
192 | "prices": {
193 | "data": [
194 | {
195 | "type": "spree_prices",
196 | "id": "33"
197 | }
198 | ]
199 | },
200 | "option_values": {
201 | "data": [
202 | {
203 | "type": "spree_option_values",
204 | "id": "1"
205 | }
206 | ]
207 | },
208 | "images": {
209 | "data": [
210 | {
211 | "type": "spree_images",
212 | "id": "1"
213 | }
214 | ]
215 | },
216 | "product": {
217 | "data": {
218 | "type": "spree_products",
219 | "id": "3"
220 | }
221 | }
222 | }
223 | }
224 | }
225 | ```
226 |
227 | Show a variant that belongs to an option value via the option value's `id`.
228 |
--------------------------------------------------------------------------------
/docs/source/includes/_images.md:
--------------------------------------------------------------------------------
1 | # Images
2 |
3 | ## List Images
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/images"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_images",
15 | "attributes": {
16 | "position": 1,
17 | "alt": null,
18 | "links": {
19 | "original": "/spree/products/1/original/ror_baseball_jersey_red.png?1442035822",
20 | "mini": "/spree/products/1/mini/ror_baseball_jersey_red.png?1442035822",
21 | "small": "/spree/products/1/small/ror_baseball_jersey_red.png?1442035822",
22 | "product": "/spree/products/1/product/ror_baseball_jersey_red.png?1442035822",
23 | "large": "/spree/products/1/large/ror_baseball_jersey_red.png?1442035822"
24 | }
25 | },
26 | "relationships": {
27 | "viewable": {
28 | "data": {
29 | "type": "spree_variants",
30 | "id": "1"
31 | }
32 | }
33 | }
34 | }
35 | ]
36 | }
37 | ```
38 |
39 | List all of the images stored in the database.
40 |
41 | ## Show Image
42 |
43 | ```shell
44 | curl "https://example.com/api/v2/images/1"
45 | ```
46 |
47 | ```json
48 | {
49 | "data": {
50 | "id": "1",
51 | "type": "spree_images",
52 | "attributes": {
53 | "position": 1,
54 | "alt": null,
55 | "links": {
56 | "original": "/spree/products/1/original/ror_baseball_jersey_red.png?1442035822",
57 | "mini": "/spree/products/1/mini/ror_baseball_jersey_red.png?1442035822",
58 | "small": "/spree/products/1/small/ror_baseball_jersey_red.png?1442035822",
59 | "product": "/spree/products/1/product/ror_baseball_jersey_red.png?1442035822",
60 | "large": "/spree/products/1/large/ror_baseball_jersey_red.png?1442035822"
61 | }
62 | },
63 | "relationships": {
64 | "viewable": {
65 | "data": {
66 | "type": "spree_variants",
67 | "id": "1"
68 | }
69 | }
70 | }
71 | }
72 | }
73 | ```
74 |
75 | Fetch an image and its links via the image's `id`.
76 |
77 | ## Show Variant of an Image
78 |
79 | ```shell
80 | curl "https://example.com/api/v2/images/1/variant"
81 | ```
82 |
83 | ```json
84 | {
85 | "data": {
86 | "id": "17",
87 | "type": "spree_variants",
88 | "attributes": {
89 | "sku": "ROR-00001",
90 | "weight": "0.0",
91 | "height": null,
92 | "width": null,
93 | "depth": null,
94 | "is_master": false,
95 | "position": 2,
96 | "name": "Ruby on Rails Baseball Jersey",
97 | "price": null,
98 | "display_price": "$0.00 CAD"
99 | },
100 | "relationships": {
101 | "prices": {
102 | "data": [
103 | {
104 | "type": "spree_prices",
105 | "id": "33"
106 | }
107 | ]
108 | },
109 | "option_values": {
110 | "data": [
111 | {
112 | "type": "spree_option_values",
113 | "id": "1"
114 | }
115 | ]
116 | },
117 | "images": {
118 | "data": [
119 | {
120 | "type": "spree_images",
121 | "id": "1"
122 | }
123 | ]
124 | },
125 | "product": {
126 | "data": {
127 | "type": "spree_products",
128 | "id": "3"
129 | }
130 | }
131 | }
132 | }
133 | }
134 | ```
135 |
136 | Fetch the variant that an image belongs to via the variant's `id`.
137 |
138 | ## Show Product of an Image
139 |
140 | ```shell
141 | curl "https://example.com/api/v2/images/1/product"
142 | ```
143 |
144 | ```json
145 | {
146 | "data": {
147 | "id": "3",
148 | "type": "spree_products",
149 | "attributes": {
150 | "name": "Ruby on Rails Baseball Jersey",
151 | "description": "Dolorem molestias sint maxime id at rem qui exercitationem. Neque voluptas corrupti magni suscipit iusto voluptatum. Ea quibusdam dolorem inventore praesentium sed dicta eveniet et. Rerum inventore laudantium quisquam earum consequatur dignissimos.",
152 | "slug": "ruby-on-rails-baseball-jersey",
153 | "meta_description": null,
154 | "meta_keywords": null,
155 | "store_name": "Whole New Home"
156 | },
157 | "relationships": {
158 | "master": {
159 | "data": {
160 | "type": "spree_variants",
161 | "id": "3"
162 | }
163 | },
164 | "variants": {
165 | "data": [
166 | {
167 | "type": "spree_variants",
168 | "id": "1"
169 | }
170 | ]
171 | },
172 | "taxons": {
173 | "data": [
174 | {
175 | "type": "spree_taxons",
176 | "id": "53"
177 | }
178 | ]
179 | },
180 | "option_types": {
181 | "data": [
182 | {
183 | "type": "spree_option_types",
184 | "id": "1"
185 | }
186 | ]
187 | },
188 | "images": {
189 | "data": [
190 | {
191 | "type": "spree_images",
192 | "id": "24"
193 | }
194 | ]
195 | }
196 | }
197 | }
198 | }
199 | ```
200 |
201 | Find the product that image belongs to via the image's `id`.
202 |
--------------------------------------------------------------------------------
/docs/font-selection.json:
--------------------------------------------------------------------------------
1 | {
2 | "IcoMoonType": "selection",
3 | "icons": [
4 | {
5 | "icon": {
6 | "paths": [
7 | "M438.857 73.143q119.429 0 220.286 58.857t159.714 159.714 58.857 220.286-58.857 220.286-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857zM512 785.714v-108.571q0-8-5.143-13.429t-12.571-5.429h-109.714q-7.429 0-13.143 5.714t-5.714 13.143v108.571q0 7.429 5.714 13.143t13.143 5.714h109.714q7.429 0 12.571-5.429t5.143-13.429zM510.857 589.143l10.286-354.857q0-6.857-5.714-10.286-5.714-4.571-13.714-4.571h-125.714q-8 0-13.714 4.571-5.714 3.429-5.714 10.286l9.714 354.857q0 5.714 5.714 10t13.714 4.286h105.714q8 0 13.429-4.286t6-10z"
8 | ],
9 | "attrs": [],
10 | "isMulticolor": false,
11 | "tags": [
12 | "exclamation-circle"
13 | ],
14 | "defaultCode": 61546,
15 | "grid": 14
16 | },
17 | "attrs": [],
18 | "properties": {
19 | "id": 100,
20 | "order": 4,
21 | "prevSize": 28,
22 | "code": 58880,
23 | "name": "exclamation-sign",
24 | "ligatures": ""
25 | },
26 | "setIdx": 0,
27 | "iconIdx": 0
28 | },
29 | {
30 | "icon": {
31 | "paths": [
32 | "M585.143 786.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-54.857v-292.571q0-8-5.143-13.143t-13.143-5.143h-182.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h54.857v182.857h-54.857q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h256q8 0 13.143-5.143t5.143-13.143zM512 274.286v-91.429q0-8-5.143-13.143t-13.143-5.143h-109.714q-8 0-13.143 5.143t-5.143 13.143v91.429q0 8 5.143 13.143t13.143 5.143h109.714q8 0 13.143-5.143t5.143-13.143zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
33 | ],
34 | "attrs": [],
35 | "isMulticolor": false,
36 | "tags": [
37 | "info-circle"
38 | ],
39 | "defaultCode": 61530,
40 | "grid": 14
41 | },
42 | "attrs": [],
43 | "properties": {
44 | "id": 85,
45 | "order": 3,
46 | "name": "info-sign",
47 | "prevSize": 28,
48 | "code": 58882
49 | },
50 | "setIdx": 0,
51 | "iconIdx": 2
52 | },
53 | {
54 | "icon": {
55 | "paths": [
56 | "M733.714 419.429q0-16-10.286-26.286l-52-51.429q-10.857-10.857-25.714-10.857t-25.714 10.857l-233.143 232.571-129.143-129.143q-10.857-10.857-25.714-10.857t-25.714 10.857l-52 51.429q-10.286 10.286-10.286 26.286 0 15.429 10.286 25.714l206.857 206.857q10.857 10.857 25.714 10.857 15.429 0 26.286-10.857l310.286-310.286q10.286-10.286 10.286-25.714zM877.714 512q0 119.429-58.857 220.286t-159.714 159.714-220.286 58.857-220.286-58.857-159.714-159.714-58.857-220.286 58.857-220.286 159.714-159.714 220.286-58.857 220.286 58.857 159.714 159.714 58.857 220.286z"
57 | ],
58 | "attrs": [],
59 | "isMulticolor": false,
60 | "tags": [
61 | "check-circle"
62 | ],
63 | "defaultCode": 61528,
64 | "grid": 14
65 | },
66 | "attrs": [],
67 | "properties": {
68 | "id": 83,
69 | "order": 9,
70 | "prevSize": 28,
71 | "code": 58886,
72 | "name": "ok-sign"
73 | },
74 | "setIdx": 0,
75 | "iconIdx": 6
76 | },
77 | {
78 | "icon": {
79 | "paths": [
80 | "M658.286 475.429q0-105.714-75.143-180.857t-180.857-75.143-180.857 75.143-75.143 180.857 75.143 180.857 180.857 75.143 180.857-75.143 75.143-180.857zM950.857 950.857q0 29.714-21.714 51.429t-51.429 21.714q-30.857 0-51.429-21.714l-196-195.429q-102.286 70.857-228 70.857-81.714 0-156.286-31.714t-128.571-85.714-85.714-128.571-31.714-156.286 31.714-156.286 85.714-128.571 128.571-85.714 156.286-31.714 156.286 31.714 128.571 85.714 85.714 128.571 31.714 156.286q0 125.714-70.857 228l196 196q21.143 21.143 21.143 51.429z"
81 | ],
82 | "width": 951,
83 | "attrs": [],
84 | "isMulticolor": false,
85 | "tags": [
86 | "search"
87 | ],
88 | "defaultCode": 61442,
89 | "grid": 14
90 | },
91 | "attrs": [],
92 | "properties": {
93 | "id": 2,
94 | "order": 1,
95 | "prevSize": 28,
96 | "code": 58887,
97 | "name": "icon-search"
98 | },
99 | "setIdx": 0,
100 | "iconIdx": 7
101 | }
102 | ],
103 | "height": 1024,
104 | "metadata": {
105 | "name": "slate",
106 | "license": "SIL OFL 1.1"
107 | },
108 | "preferences": {
109 | "showGlyphs": true,
110 | "showQuickUse": true,
111 | "showQuickUse2": true,
112 | "showSVGs": true,
113 | "fontPref": {
114 | "prefix": "icon-",
115 | "metadata": {
116 | "fontFamily": "slate",
117 | "majorVersion": 1,
118 | "minorVersion": 0,
119 | "description": "Based on FontAwesome",
120 | "license": "SIL OFL 1.1"
121 | },
122 | "metrics": {
123 | "emSize": 1024,
124 | "baseline": 6.25,
125 | "whitespace": 50
126 | },
127 | "resetPoint": 58880,
128 | "showSelector": false,
129 | "selector": "class",
130 | "classSelector": ".icon",
131 | "showMetrics": false,
132 | "showMetadata": true,
133 | "showVersion": true,
134 | "ie7": false
135 | },
136 | "imagePref": {
137 | "prefix": "icon-",
138 | "png": true,
139 | "useClassSelector": true,
140 | "color": 4473924,
141 | "bgColor": 16777215
142 | },
143 | "historySize": 100,
144 | "showCodes": true,
145 | "gridSize": 16,
146 | "showLiga": false
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/docs/source/javascripts/app/_lang.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | */
16 | (function (global) {
17 | 'use strict';
18 |
19 | var languages = [];
20 |
21 | global.setupLanguages = setupLanguages;
22 | global.activateLanguage = activateLanguage;
23 |
24 | function activateLanguage(language) {
25 | if (!language) return;
26 | if (language === "") return;
27 |
28 | $(".lang-selector a").removeClass('active');
29 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active');
30 | for (var i=0; i < languages.length; i++) {
31 | $(".highlight." + languages[i]).hide();
32 | }
33 | $(".highlight." + language).show();
34 |
35 | global.toc.calculateHeights();
36 |
37 | // scroll to the new location of the position
38 | if ($(window.location.hash).get(0)) {
39 | $(window.location.hash).get(0).scrollIntoView(true);
40 | }
41 | }
42 |
43 | // parseURL and stringifyURL are from https://github.com/sindresorhus/query-string
44 | // MIT licensed
45 | // https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license
46 | function parseURL(str) {
47 | if (typeof str !== 'string') {
48 | return {};
49 | }
50 |
51 | str = str.trim().replace(/^(\?|#|&)/, '');
52 |
53 | if (!str) {
54 | return {};
55 | }
56 |
57 | return str.split('&').reduce(function (ret, param) {
58 | var parts = param.replace(/\+/g, ' ').split('=');
59 | var key = parts[0];
60 | var val = parts[1];
61 |
62 | key = decodeURIComponent(key);
63 | // missing `=` should be `null`:
64 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
65 | val = val === undefined ? null : decodeURIComponent(val);
66 |
67 | if (!ret.hasOwnProperty(key)) {
68 | ret[key] = val;
69 | } else if (Array.isArray(ret[key])) {
70 | ret[key].push(val);
71 | } else {
72 | ret[key] = [ret[key], val];
73 | }
74 |
75 | return ret;
76 | }, {});
77 | };
78 |
79 | function stringifyURL(obj) {
80 | return obj ? Object.keys(obj).sort().map(function (key) {
81 | var val = obj[key];
82 |
83 | if (Array.isArray(val)) {
84 | return val.sort().map(function (val2) {
85 | return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
86 | }).join('&');
87 | }
88 |
89 | return encodeURIComponent(key) + '=' + encodeURIComponent(val);
90 | }).join('&') : '';
91 | };
92 |
93 | // gets the language set in the query string
94 | function getLanguageFromQueryString() {
95 | if (location.search.length >= 1) {
96 | var language = parseURL(location.search).language
97 | if (language) {
98 | return language;
99 | } else if (jQuery.inArray(location.search.substr(1), languages) != -1) {
100 | return location.search.substr(1);
101 | }
102 | }
103 |
104 | return false;
105 | }
106 |
107 | // returns a new query string with the new language in it
108 | function generateNewQueryString(language) {
109 | var url = parseURL(location.search);
110 | if (url.language) {
111 | url.language = language;
112 | return stringifyURL(url);
113 | }
114 | return language;
115 | }
116 |
117 | // if a button is clicked, add the state to the history
118 | function pushURL(language) {
119 | if (!history) { return; }
120 | var hash = window.location.hash;
121 | if (hash) {
122 | hash = hash.replace(/^#+/, '');
123 | }
124 | history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash);
125 |
126 | // save language as next default
127 | localStorage.setItem("language", language);
128 | }
129 |
130 | function setupLanguages(l) {
131 | var defaultLanguage = localStorage.getItem("language");
132 |
133 | languages = l;
134 |
135 | var presetLanguage = getLanguageFromQueryString();
136 | if (presetLanguage) {
137 | // the language is in the URL, so use that language!
138 | activateLanguage(presetLanguage);
139 |
140 | localStorage.setItem("language", presetLanguage);
141 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) {
142 | // the language was the last selected one saved in localstorage, so use that language!
143 | activateLanguage(defaultLanguage);
144 | } else {
145 | // no language selected, so use the default
146 | activateLanguage(languages[0]);
147 | }
148 | }
149 |
150 | // if we click on a language tab, activate that language
151 | $(function() {
152 | $(".lang-selector a").on("click", function() {
153 | var language = $(this).data("language-name");
154 | pushURL(language);
155 | activateLanguage(language);
156 | return false;
157 | });
158 | window.onpopstate = function() {
159 | activateLanguage(getLanguageFromQueryString());
160 | };
161 | });
162 | })(window);
163 |
--------------------------------------------------------------------------------
/docs/source/javascripts/lib/_energize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * energize.js v0.1.0
3 | *
4 | * Speeds up click events on mobile devices.
5 | * https://github.com/davidcalhoun/energize.js
6 | */
7 |
8 | (function() { // Sandbox
9 | /**
10 | * Don't add to non-touch devices, which don't need to be sped up
11 | */
12 | if(!('ontouchstart' in window)) return;
13 |
14 | var lastClick = {},
15 | isThresholdReached, touchstart, touchmove, touchend,
16 | click, closest;
17 |
18 | /**
19 | * isThresholdReached
20 | *
21 | * Compare touchstart with touchend xy coordinates,
22 | * and only fire simulated click event if the coordinates
23 | * are nearby. (don't want clicking to be confused with a swipe)
24 | */
25 | isThresholdReached = function(startXY, xy) {
26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5;
27 | };
28 |
29 | /**
30 | * touchstart
31 | *
32 | * Save xy coordinates when the user starts touching the screen
33 | */
34 | touchstart = function(e) {
35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY];
36 | this.threshold = false;
37 | };
38 |
39 | /**
40 | * touchmove
41 | *
42 | * Check if the user is scrolling past the threshold.
43 | * Have to check here because touchend will not always fire
44 | * on some tested devices (Kindle Fire?)
45 | */
46 | touchmove = function(e) {
47 | // NOOP if the threshold has already been reached
48 | if(this.threshold) return false;
49 |
50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]);
51 | };
52 |
53 | /**
54 | * touchend
55 | *
56 | * If the user didn't scroll past the threshold between
57 | * touchstart and touchend, fire a simulated click.
58 | *
59 | * (This will fire before a native click)
60 | */
61 | touchend = function(e) {
62 | // Don't fire a click if the user scrolled past the threshold
63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) {
64 | return;
65 | }
66 |
67 | /**
68 | * Create and fire a click event on the target element
69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent
70 | */
71 | var touch = e.changedTouches[0],
72 | evt = document.createEvent('MouseEvents');
73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click
75 | e.target.dispatchEvent(evt);
76 | };
77 |
78 | /**
79 | * click
80 | *
81 | * Because we've already fired a click event in touchend,
82 | * we need to listed for all native click events here
83 | * and suppress them as necessary.
84 | */
85 | click = function(e) {
86 | /**
87 | * Prevent ghost clicks by only allowing clicks we created
88 | * in the click event we fired (look for e.simulated)
89 | */
90 | var time = Date.now(),
91 | timeDiff = time - lastClick.time,
92 | x = e.clientX,
93 | y = e.clientY,
94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)],
95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps
96 | nodeName = target.nodeName,
97 | isLink = nodeName === 'A',
98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href");
99 |
100 | lastClick.time = time;
101 | lastClick.x = x;
102 | lastClick.y = y;
103 |
104 | /**
105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire),
106 | * so we have to add more logic to determine the time of the last click. Not perfect...
107 | *
108 | * Older, simpler check: if((!e.simulated) || standAlone)
109 | */
110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) {
111 | e.preventDefault();
112 | e.stopPropagation();
113 | if(!standAlone) return false;
114 | }
115 |
116 | /**
117 | * Special logic for standalone web apps
118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window
119 | */
120 | if(standAlone) {
121 | window.location = target.getAttribute("href");
122 | }
123 |
124 | /**
125 | * Add an energize-focus class to the targeted link (mimics :focus behavior)
126 | * TODO: test and/or remove? Does this work?
127 | */
128 | if(!target || !target.classList) return;
129 | target.classList.add("energize-focus");
130 | window.setTimeout(function(){
131 | target.classList.remove("energize-focus");
132 | }, 150);
133 | };
134 |
135 | /**
136 | * closest
137 | * @param {HTMLElement} node current node to start searching from.
138 | * @param {string} tagName the (uppercase) name of the tag you're looking for.
139 | *
140 | * Find the closest ancestor tag of a given node.
141 | *
142 | * Starts at node and goes up the DOM tree looking for a
143 | * matching nodeName, continuing until hitting document.body
144 | */
145 | closest = function(node, tagName){
146 | var curNode = node;
147 |
148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after
149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found
150 | curNode = curNode.parentNode; // not found, so keep going up
151 | }
152 |
153 | return null; // not found
154 | };
155 |
156 | /**
157 | * Add all delegated event listeners
158 | *
159 | * All the events we care about bubble up to document,
160 | * so we can take advantage of event delegation.
161 | *
162 | * Note: no need to wait for DOMContentLoaded here
163 | */
164 | document.addEventListener('touchstart', touchstart, false);
165 | document.addEventListener('touchmove', touchmove, false);
166 | document.addEventListener('touchend', touchend, false);
167 | document.addEventListener('click', click, true); // TODO: why does this use capture?
168 |
169 | })();
--------------------------------------------------------------------------------
/docs/source/includes/_option_types.md:
--------------------------------------------------------------------------------
1 | # Option Types
2 |
3 | ## List Option Types
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/option_types"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_option_types",
15 | "attributes": {
16 | "name": "tshirt-size",
17 | "presentation": "Size",
18 | "position": 1
19 | },
20 | "relationships": {
21 | "option_values": {
22 | "data": [
23 | {
24 | "type": "spree_option_values",
25 | "id": "1"
26 | }
27 | ]
28 | },
29 | "products": {
30 | "data": [
31 | {
32 | "type": "spree_products",
33 | "id": "1"
34 | }
35 | ]
36 | }
37 | }
38 | }
39 | ]
40 | }
41 | ```
42 |
43 | List all of the option types in the database.
44 |
45 | ## Show Option Type
46 |
47 | ```shell
48 | curl "https://example.com/api/v2/option_types/1"
49 | ```
50 |
51 | ```json
52 | {
53 | "data": {
54 | "id": "1",
55 | "type": "spree_option_types",
56 | "attributes": {
57 | "name": "tshirt-size",
58 | "presentation": "Size",
59 | "position": 1
60 | },
61 | "relationships": {
62 | "option_values": {
63 | "data": [
64 | {
65 | "type": "spree_option_values",
66 | "id": "1"
67 | }
68 | ]
69 | },
70 | "products": {
71 | "data": [
72 | {
73 | "type": "spree_products",
74 | "id": "1"
75 | }
76 | ]
77 | }
78 | }
79 | }
80 | }
81 | ```
82 |
83 | Fetch an option type in the database.
84 |
85 | ## List Option Values of a Option Type
86 |
87 | ```shell
88 | curl "https://example.com/api/v2/option_types/1/option_values"
89 | ```
90 |
91 | ```json
92 | {
93 | "data": [
94 | {
95 | "id": "1",
96 | "type": "spree_option_values",
97 | "attributes": {
98 | "name": "Small",
99 | "presentation": "S",
100 | "position": 1
101 | },
102 | "relationships": {
103 | "option_type": {
104 | "data": {
105 | "type": "spree_option_types",
106 | "id": "1"
107 | }
108 | }
109 | }
110 | }
111 | ]
112 | }
113 | ```
114 |
115 | See all of the option values that an option type owns.
116 |
117 | ## Show Option Values of a Option Type
118 |
119 | ```shell
120 | curl "https://example.com/api/v2/option_types/1/option_values/1"
121 | ```
122 |
123 | ```json
124 | {
125 | "data": {
126 | "id": "1",
127 | "type": "spree_option_values",
128 | "attributes": {
129 | "name": "Small",
130 | "presentation": "S",
131 | "position": 1
132 | },
133 | "relationships": {
134 | "option_type": {
135 | "data": {
136 | "type": "spree_option_types",
137 | "id": "1"
138 | }
139 | }
140 | }
141 | }
142 | }
143 | ```
144 |
145 | Fetch an option value that an option type owns.
146 |
147 | ## List Products of a Option Type
148 |
149 | ```shell
150 | curl "https://example.com/api/v2/option_types/1/products"
151 | ```
152 |
153 | ```json
154 | {
155 | "data": [
156 | {
157 | "id": "1",
158 | "type": "spree_products",
159 | "attributes": {
160 | "name": "Ruby on Rails Tote",
161 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.",
162 | "slug": "ruby-on-rails-tote",
163 | "meta_description": null,
164 | "meta_keywords": null,
165 | "store_name": "Whole New Home"
166 | },
167 | "relationships": {
168 | "master": {
169 | "data": {
170 | "type": "spree_variants",
171 | "id": "1"
172 | }
173 | },
174 | "variants": {
175 | "data": []
176 | },
177 | "taxons": {
178 | "data": [
179 | {
180 | "type": "spree_taxons",
181 | "id": "1"
182 | }
183 | ]
184 | },
185 | "option_types": {
186 | "data": [
187 | {
188 | "type": "spree_option_types",
189 | "id": "1"
190 | }
191 | ]
192 | },
193 | "images": {
194 | "data": [
195 | {
196 | "type": "spree_images",
197 | "id": "1"
198 | }
199 | ]
200 | }
201 | }
202 | }
203 | ]
204 | }
205 | ```
206 |
207 | See all of the products that an option type owns.
208 |
209 | ## Show Product of a Option Type
210 |
211 | ```shell
212 | curl "https://example.com/api/v2/option_types/1/products/1"
213 | ```
214 |
215 | ```json
216 | {
217 | "data": {
218 | "id": "1",
219 | "type": "spree_products",
220 | "attributes": {
221 | "name": "Ruby on Rails Tote",
222 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.",
223 | "slug": "ruby-on-rails-tote",
224 | "meta_description": null,
225 | "meta_keywords": null,
226 | "store_name": "Whole New Home"
227 | },
228 | "relationships": {
229 | "master": {
230 | "data": {
231 | "type": "spree_variants",
232 | "id": "1"
233 | }
234 | },
235 | "variants": {
236 | "data": []
237 | },
238 | "taxons": {
239 | "data": [
240 | {
241 | "type": "spree_taxons",
242 | "id": "1"
243 | }
244 | ]
245 | },
246 | "option_types": {
247 | "data": [
248 | {
249 | "type": "spree_option_types",
250 | "id": "1"
251 | }
252 | ]
253 | },
254 | "images": {
255 | "data": [
256 | {
257 | "type": "spree_images",
258 | "id": "1"
259 | }
260 | ]
261 | }
262 | }
263 | }
264 | }
265 | ```
266 |
267 | Find a product that an option type owns.
268 |
--------------------------------------------------------------------------------
/docs/source/layouts/layout.erb:
--------------------------------------------------------------------------------
1 | <%#
2 | Copyright 2008-2013 Concur Technologies, Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | not use this file except in compliance with the License. You may obtain
6 | a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 | %>
16 | <% language_tabs = current_page.data.language_tabs %>
17 |
18 |
19 |
20 |
21 |
22 |
23 | <%= current_page.data.title || "API Documentation" %>
24 |
25 |
26 |
27 | <%= stylesheet_link_tag :screen, media: :screen %>
28 | <%= stylesheet_link_tag :print, media: :print %>
29 |
30 | <% if current_page.data.search %>
31 | <%= javascript_include_tag "all" %>
32 | <% else %>
33 | <%= javascript_include_tag "all_nosearch" %>
34 | <% end %>
35 |
36 | <% if language_tabs %>
37 |
42 | <% end %>
43 |
44 |
45 |
46 |
47 |
48 | NAV
49 | <%= image_tag('navbar.png') %>
50 |
51 |
52 |
53 |
60 | <% if language_tabs %>
61 |
70 | <% end %>
71 | <% if current_page.data.search %>
72 |
73 |
74 |
75 |
76 | <% end %>
77 |
78 |
79 | <% if current_page.data.toc_footers %>
80 |
85 | <% end %>
86 |
87 |
88 |
89 |
90 | <%= yield %>
91 | <% current_page.data.includes && current_page.data.includes.each do |include| %>
92 | <%= partial "includes/#{include}" %>
93 | <% end %>
94 |
95 |
96 | <% if language_tabs %>
97 |
106 | <% end %>
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/docs/source/javascripts/lib/_imagesloaded.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * imagesLoaded PACKAGED v3.1.8
3 | * JavaScript is all like "You images are done yet or what?"
4 | * MIT License
5 | */
6 |
7 | (function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s});
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.3.1
4 |
5 | * Bump Active Model Serializer from rc2 to rc4.
6 |
7 | [Ben A. Morgan](https://github.com/BenMorganIO)
8 |
9 | ## 0.3.0
10 |
11 | * Support `status` and `code` for Error Objects
12 |
13 | When an error is handed back to the user, we now provide the status and the
14 | code keys. This adds another feature to the API that is supported by the
15 | JSON API specification.
16 |
17 | [Ben A. Morgan](https://github.com/BenMorganIO)
18 |
19 | * Add Friendly ID Support for `Spree::Product`
20 |
21 | When a request came in for `/api/v2/products/example-product` and there was
22 | a product in the DB with a corresponding slug, it would return a 404. The
23 | controller now calls `Spree::Product.friendly` before rendering any
24 | instance.
25 |
26 | [Ben A. Morgan](https://github.com/BenMorganIO)
27 |
28 | * Drop Spree Support
29 |
30 | After trying to get the spree support working, I’ve concluded that its just
31 | not worth it. I know there’s a lot of you out there that really want this.
32 | If you truly need to keep using Spree, please fork this repo and make the
33 | necessary changes.
34 |
35 | [Ben A. Morgan](https://github.com/BenMorganIO)
36 |
37 | * The project has been renamed from `spree_api_v2` to `solidus_json_api`.
38 | Since the Spree project is no longer maintained, then there's no reason to
39 | give Spree a second API version. This means that you need to update the
40 | following code:
41 |
42 | ```ruby
43 | ### Before ###
44 |
45 | # Gemfile
46 | gem 'spree_api_v2'
47 |
48 | # config/initializers/spree_api_v2.rb
49 | SolidusApiV2.setup do |config|
50 | config.parent_serializer = ApplicationSerializer
51 | end
52 |
53 | ### After ###
54 |
55 | # Gemfile
56 | gem 'solidus_json_api'
57 |
58 | # config/initializers/solidus_json_api.rb
59 | SolidusJsonApi.setup do |config|
60 | config.parent_serializer = ApplicationSerializer
61 | end
62 | ```
63 |
64 | [Ben A. Morgan](https://github.com/BenMorganIO)
65 |
66 | * In Spree, you will receive an error if the stock is not sufficient when
67 | updating a line item. This is done in this project since the API requires
68 | it. However, when transition to Solidus, we discovered that this feature was
69 | constrained to the complete state. We've added a validation check before
70 | saving a line item. Please open an issue if you believe this to be an
71 | anti-pattern.
72 |
73 | [Ben A. Morgan](https://github.com/BenMorganIO)
74 |
75 | * Removed `Spree::Order#considered_risky` from the JSON response since Solidus
76 | doesn't support this anymore.
77 |
78 | [Ben A. Morgan](https://github.com/BenMorganIO)
79 |
80 | * `Spree::Variant#depth`, `Spree::Variant#width`, and `Spree::Variant#height`
81 | now return `null` instead of `""` when empty.
82 |
83 | ```json
84 | // Before
85 | {
86 | "type" : "spree_variant",
87 | "data" : {
88 | "attributes" : {
89 | "depth" : "",
90 | "width" : "",
91 | "height" : "",
92 | ...
93 | }
94 | }
95 | }
96 |
97 | // After
98 | {
99 | "type" : "spree_variant",
100 | "data" : {
101 | "attributes" : {
102 | "depth" : null,
103 | "width" : null,
104 | "height" : null,
105 | ...
106 | }
107 | }
108 | }
109 | ```
110 |
111 | [Ben A. Morgan](https://github.com/BenMorganIO)
112 |
113 | * `Spree::LineItem#additional_tax_total` and `Spree::LineItem#adjustment_total`
114 | now return as strings instead of floats in the api:
115 |
116 | ```json
117 | // Before
118 | {
119 | "type" : "spree_line_item",
120 | "data" : {
121 | "attributes" : {
122 | "additional_tax_total" : 0,
123 | "adjustment_total" : 0.0,
124 | ...
125 | }
126 | }
127 | }
128 |
129 | // After
130 | {
131 | "type" : "spree_line_item",
132 | "data" : {
133 | "attributes" : {
134 | "additional_tax_total" : "0",
135 | "adjustment_total" : "0.0",
136 | ...
137 | }
138 | }
139 | }
140 | ```
141 |
142 | [Ben A. Morgan](https://github.com/BenMorganIO)
143 |
144 | * For Orders, you can now list them. A user can only view their own orders.
145 | If the user is an admin, they can view all of the orders.
146 |
147 | [Ben A. Morgan](https://github.com/BenMorganIO)
148 |
149 | * POST `/api/v2/line_items` now requires JSON API format instead of the rails
150 | format.
151 |
152 | ```shell
153 | # Before
154 | curl "https://example.com/api/v2/line_items"
155 | -X POST
156 | -d token=abc123
157 | -d line_item[order_id]=1
158 | -d line_item[variant_id]=1
159 | -d line_item[quantity]=1
160 |
161 | # After
162 | curl "https://example.com/api/v2/line_items"
163 | -X POST
164 | -d token=abc123
165 | -d data[attributes][order_id]=1
166 | -d data[attributes][variant_id]=1
167 | -d data[attributes][quantity]=1
168 | ```
169 |
170 | [Ben A. Morgan](https://github.com/BenMorganIO)
171 |
172 | ## 0.2.2
173 |
174 | * The parent serializer for `Spree::BaseSerializer` is now configurable.
175 | In some instances, you may have an `ApplicationSerializer`.
176 | To make the `Spree::BaseSerializer` use an `ApplicationSerializer` instead
177 | of the `ActiveModel::Serializer`:
178 |
179 | ```ruby
180 | # config/initializers/spree_api_v2.rb; or
181 | # config/initializers/solidus_api_v2.rb
182 | SpreeApiV2.setup do |config|
183 | config.parent_serializer = ApplicationSerializer
184 | end
185 | ```
186 |
187 | [Ben A. Morgan](https://github.com/BenMorganIO)
188 |
189 | ## 0.2.1
190 |
191 | * Fixes a bug where the user serializer wasn't extending from the `Spree::BaseSerializer` this is now fixed.
192 |
193 | [Ben A. Morgan](https://github.com/BenMorganIO)
194 |
195 | ## 0.2.0
196 |
197 | * Calling `super` inside fo `#index` and `#show` actions is now deprecated.
198 | Since these are methods that are meant to be called, they have been renamed to `#render_collection` and `#render_instance`.
199 |
200 | [Ben A. Morgan](https://github.com/BenMorganIO)
201 |
202 | * Added support for [Solidus](https://github.com/solidusio/solidus).
203 |
204 | [Ben A. Morgan](https://github.com/BenMorganIO)
205 |
206 | * In some instances, you would like to use the helper methods provided by the Spree V2 API, but unable to extend from the controller.
207 | The render methods have been moved to a concern.
208 | If you would like to include them in a controller without having to extend from the `Spree::Api::V2::BaseController`, then:
209 |
210 | ```ruby
211 | module Api
212 | class MyController < Api::BaseController
213 | # include the concern
214 | include Spree::Api::V2::Renderable
215 |
216 | # example usage of rendering all products without calling
217 | # +render_instance+ but providing page detail information that conforms
218 | # to the JSON API spec.
219 | def index
220 | render json: @products, meta: { page: page_details(@products) }
221 | end
222 | end
223 | end
224 | ```
225 |
226 | [Ben A. Morgan](https://github.com/BenMorganIO)
227 |
228 | ## 0.1.0
229 |
230 | Initial Release
231 |
--------------------------------------------------------------------------------
/docs/source/includes/_taxonomies.md:
--------------------------------------------------------------------------------
1 | # Taxonomies
2 |
3 | ## List Taxonomies
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/taxonomies"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "1",
14 | "type": "spree_taxonomies",
15 | "attributes": {
16 | "name": "Category",
17 | "position": 1
18 | },
19 | "relationships": {
20 | "taxons": {
21 | "data": [
22 | {
23 | "type": "spree_taxons",
24 | "id": "2"
25 | },
26 | {
27 | "type": "spree_taxons",
28 | "id": "1"
29 | }
30 | ]
31 | },
32 | "children": {
33 | "data": [
34 | {
35 | "type": "spree_taxons",
36 | "id": "2"
37 | }
38 | ]
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | ```
45 |
46 | List of the taxonomies in the database.
47 |
48 | ## Show Taxonomy
49 |
50 | ```shell
51 | curl "https://example.com/api/v2/taxonomies/1"
52 | ```
53 |
54 | ```json
55 | {
56 | "data": {
57 | "id": "1",
58 | "type": "spree_taxonomies",
59 | "attributes": {
60 | "name": "Category",
61 | "position": 1
62 | },
63 | "relationships": {
64 | "taxons": {
65 | "data": [
66 | {
67 | "type": "spree_taxons",
68 | "id": "2"
69 | },
70 | {
71 | "type": "spree_taxons",
72 | "id": "1"
73 | }
74 | ]
75 | },
76 | "children": {
77 | "data": [
78 | {
79 | "type": "spree_taxons",
80 | "id": "2"
81 | }
82 | ]
83 | }
84 | }
85 | }
86 | }
87 | ```
88 |
89 | Select a taxonomy via the `id`.
90 |
91 | ## List Taxons of a Taxonomy
92 |
93 | ```shell
94 | curl "https://example.com/api/v2/taxonomies/1/taxons"
95 | ```
96 |
97 | ```json
98 | {
99 | "data": [
100 | {
101 | "id": "2",
102 | "type": "spree_taxons",
103 | "attributes": {
104 | "name": "SLEEP",
105 | "permalink": "category/sleep",
106 | "position": 0,
107 | "description": null,
108 | "meta_title": null,
109 | "meta_description": null,
110 | "meta_keywords": null,
111 | "depth": 1,
112 | "classifications_count": null
113 | },
114 | "relationships": {
115 | "taxonomy": {
116 | "data": {
117 | "type": "spree_taxonomies",
118 | "id": "1"
119 | }
120 | },
121 | "parent": {
122 | "data": {
123 | "type": "spree_taxons",
124 | "id": "1"
125 | }
126 | },
127 | "children": {
128 | "data": [
129 | {
130 | "type": "spree_taxons",
131 | "id": "3"
132 | }
133 | ]
134 | }
135 | }
136 | },
137 | {
138 | "id": "1",
139 | "type": "spree_taxons",
140 | "attributes": {
141 | "name": "Category",
142 | "permalink": "category",
143 | "position": 0,
144 | "description": null,
145 | "meta_title": null,
146 | "meta_description": null,
147 | "meta_keywords": null,
148 | "depth": 0,
149 | "classifications_count": 1
150 | },
151 | "relationships": {
152 | "taxonomy": {
153 | "data": {
154 | "type": "spree_taxonomies",
155 | "id": "1"
156 | }
157 | },
158 | "parent": {
159 | "data": null
160 | },
161 | "children": {
162 | "data": [
163 | {
164 | "type": "spree_taxons",
165 | "id": "2"
166 | }
167 | ]
168 | }
169 | }
170 | }
171 | ]
172 | }
173 | ```
174 |
175 | List all of the taxons that a taxonomy has a reference to.
176 |
177 | ## Show Taxon of a Taxonomy
178 |
179 | ```shell
180 | curl "https://example.com/api/v2/taxonomies/1/taxons/1"
181 | ```
182 |
183 | ```json
184 | {
185 | "data": {
186 | "id": "1",
187 | "type": "spree_taxons",
188 | "attributes": {
189 | "name": "Category",
190 | "permalink": "category",
191 | "position": 0,
192 | "description": null,
193 | "meta_title": null,
194 | "meta_description": null,
195 | "meta_keywords": null,
196 | "depth": 0,
197 | "classifications_count": 1
198 | },
199 | "relationships": {
200 | "taxonomy": {
201 | "data": {
202 | "type": "spree_taxonomies",
203 | "id": "1"
204 | }
205 | },
206 | "parent": {
207 | "data": null
208 | },
209 | "children": {
210 | "data": [
211 | {
212 | "type": "spree_taxons",
213 | "id": "2"
214 | }
215 | ]
216 | }
217 | }
218 | }
219 | }
220 | ```
221 |
222 | Find a taxon that the taxonomy owns via the taxonomy's `id` and the taxon's `id`.
223 |
224 | ## List Children of a Taxonomy
225 |
226 | ```shell
227 | curl "https://example.com/api/v2/taxonomies/1/children"
228 | ```
229 |
230 | ```json
231 | {
232 | "data": [
233 | {
234 | "id": "2",
235 | "type": "spree_taxons",
236 | "attributes": {
237 | "name": "SLEEP",
238 | "permalink": "category/sleep",
239 | "position": 0,
240 | "description": null,
241 | "meta_title": null,
242 | "meta_description": null,
243 | "meta_keywords": null,
244 | "depth": 1,
245 | "classifications_count": null
246 | },
247 | "relationships": {
248 | "taxonomy": {
249 | "data": {
250 | "type": "spree_taxonomies",
251 | "id": "1"
252 | }
253 | },
254 | "parent": {
255 | "data": {
256 | "type": "spree_taxons",
257 | "id": "1"
258 | }
259 | },
260 | "children": {
261 | "data": [
262 | {
263 | "type": "spree_taxons",
264 | "id": "3"
265 | }
266 | ]
267 | }
268 | }
269 | }
270 | ]
271 | }
272 | ```
273 |
274 | List all of the children of a taxonomy.
275 | This does _not_ include the root taxon that would be included when listing all of the taxons of this taxonomy.
276 |
277 | ## Show Child of a Taxonomy
278 |
279 | ```shell
280 | curl "https://example.com/api/v2/taxonomies/1/children/2"
281 | ```
282 |
283 | ```json
284 | {
285 | "data": {
286 | "id": "2",
287 | "type": "spree_taxons",
288 | "attributes": {
289 | "name": "SLEEP",
290 | "permalink": "category/sleep",
291 | "position": 0,
292 | "description": null,
293 | "meta_title": null,
294 | "meta_description": null,
295 | "meta_keywords": null,
296 | "depth": 1,
297 | "classifications_count": null
298 | },
299 | "relationships": {
300 | "taxonomy": {
301 | "data": {
302 | "type": "spree_taxonomies",
303 | "id": "1"
304 | }
305 | },
306 | "parent": {
307 | "data": {
308 | "type": "spree_taxons",
309 | "id": "1"
310 | }
311 | },
312 | "children": {
313 | "data": [
314 | {
315 | "type": "spree_taxons",
316 | "id": "3"
317 | }
318 | ]
319 | }
320 | }
321 | }
322 | }
323 | ```
324 |
325 | Fetch a child of the taxnomy via the taxonomy's `id` and the child's `id`.
326 |
--------------------------------------------------------------------------------
/docs/source/includes/_taxons.md:
--------------------------------------------------------------------------------
1 | # Taxons
2 |
3 | ## List Taxons
4 |
5 | ```shell
6 | curl "https://example.com/api/v2/taxons"
7 | ```
8 |
9 | ```json
10 | {
11 | "data": [
12 | {
13 | "id": "2",
14 | "type": "spree_taxons",
15 | "attributes": {
16 | "name": "SLEEP",
17 | "permalink": "category/sleep",
18 | "position": 0,
19 | "description": null,
20 | "meta_title": null,
21 | "meta_description": null,
22 | "meta_keywords": null,
23 | "depth": 1,
24 | "classifications_count": null
25 | },
26 | "relationships": {
27 | "taxonomy": {
28 | "data": {
29 | "type": "spree_taxonomies",
30 | "id": "1"
31 | }
32 | },
33 | "parent": {
34 | "data": {
35 | "type": "spree_taxons",
36 | "id": "1"
37 | }
38 | },
39 | "children": {
40 | "data": [
41 | {
42 | "type": "spree_taxons",
43 | "id": "3"
44 | }
45 | ]
46 | }
47 | }
48 | }
49 | ]
50 | }
51 | ```
52 |
53 | List all the taxons in the database.
54 |
55 | ## Show Taxon
56 |
57 | ```shell
58 | curl "https://example.com/api/v2/taxons/1"
59 | ```
60 |
61 | ```json
62 | {
63 | "data": {
64 | "id": "1",
65 | "type": "spree_taxons",
66 | "attributes": {
67 | "name": "Category",
68 | "permalink": "category",
69 | "position": 0,
70 | "description": null,
71 | "meta_title": null,
72 | "meta_description": null,
73 | "meta_keywords": null,
74 | "depth": 0,
75 | "classifications_count": 1
76 | },
77 | "relationships": {
78 | "taxonomy": {
79 | "data": {
80 | "type": "spree_taxonomies",
81 | "id": "1"
82 | }
83 | },
84 | "parent": {
85 | "data": null
86 | },
87 | "children": {
88 | "data": [
89 | {
90 | "type": "spree_taxons",
91 | "id": "2"
92 | }
93 | ]
94 | }
95 | }
96 | }
97 | }
98 | ```
99 |
100 | Locate the taxon that you're looking for via its `id`.
101 |
102 | ## Show Taxonomy of a Taxon
103 |
104 | ```shell
105 | curl "https://example.com/api/v2/taxons/1/taxonomy"
106 | ```
107 |
108 | ```json
109 | {
110 | "data": {
111 | "id": "1",
112 | "type": "spree_taxonomies",
113 | "attributes": {
114 | "name": "Category",
115 | "position": 1
116 | },
117 | "relationships": {
118 | "taxons": {
119 | "data": [
120 | {
121 | "type": "spree_taxons",
122 | "id": "2"
123 | },
124 | {
125 | "type": "spree_taxons",
126 | "id": "1"
127 | }
128 | ]
129 | },
130 | "children": {
131 | "data": [
132 | {
133 | "type": "spree_taxons",
134 | "id": "2"
135 | }
136 | ]
137 | }
138 | }
139 | }
140 | }
141 | ```
142 |
143 | Display the taxonomy that a taxon belongs.
144 |
145 | ## Show Parent of a Taxon
146 |
147 | ```shell
148 | curl "https://example.com/api/v2/taxons/2/parent"
149 | ```
150 |
151 | ```json
152 | {
153 | "data": {
154 | "id": "1",
155 | "type": "spree_taxons",
156 | "attributes": {
157 | "name": "Category",
158 | "permalink": "category",
159 | "position": 0,
160 | "description": null,
161 | "meta_title": null,
162 | "meta_description": null,
163 | "meta_keywords": null,
164 | "depth": 0,
165 | "classifications_count": 1
166 | },
167 | "relationships": {
168 | "taxonomy": {
169 | "data": {
170 | "type": "spree_taxonomies",
171 | "id": "1"
172 | }
173 | },
174 | "parent": {
175 | "data": null
176 | },
177 | "children": {
178 | "data": [
179 | {
180 | "type": "spree_taxons",
181 | "id": "2"
182 | }
183 | ]
184 | }
185 | }
186 | }
187 | }
188 | ```
189 |
190 | Display the parent taxon that a taxon may belong to.
191 |
192 | ## List Children of a Taxon
193 |
194 | ```shell
195 | curl "https://example.com/api/v2/taxons/1/children"
196 | ```
197 |
198 | ```json
199 | {
200 | "data": [
201 | {
202 | "id": "2",
203 | "type": "spree_taxons",
204 | "attributes": {
205 | "name": "SLEEP",
206 | "permalink": "category/sleep",
207 | "position": 0,
208 | "description": null,
209 | "meta_title": null,
210 | "meta_description": null,
211 | "meta_keywords": null,
212 | "depth": 1,
213 | "classifications_count": null
214 | },
215 | "relationships": {
216 | "taxonomy": {
217 | "data": {
218 | "type": "spree_taxonomies",
219 | "id": "1"
220 | }
221 | },
222 | "parent": {
223 | "data": {
224 | "type": "spree_taxons",
225 | "id": "1"
226 | }
227 | },
228 | "children": {
229 | "data": [
230 | {
231 | "type": "spree_taxons",
232 | "id": "3"
233 | }
234 | ]
235 | }
236 | }
237 | }
238 | ]
239 | }
240 | ```
241 |
242 | List all the children that a taxon has.
243 |
244 | ## Show Child of a Taxon
245 |
246 | ```shell
247 | curl "https://example.com/api/v2/taxons/1/children/2"
248 | ```
249 |
250 | ```json
251 | {
252 | "data": {
253 | "id": "2",
254 | "type": "spree_taxons",
255 | "attributes": {
256 | "name": "SLEEP",
257 | "permalink": "category/sleep",
258 | "position": 0,
259 | "description": null,
260 | "meta_title": null,
261 | "meta_description": null,
262 | "meta_keywords": null,
263 | "depth": 1,
264 | "classifications_count": null
265 | },
266 | "relationships": {
267 | "taxonomy": {
268 | "data": {
269 | "type": "spree_taxonomies",
270 | "id": "1"
271 | }
272 | },
273 | "parent": {
274 | "data": {
275 | "type": "spree_taxons",
276 | "id": "1"
277 | }
278 | },
279 | "children": {
280 | "data": [
281 | {
282 | "type": "spree_taxons",
283 | "id": "3"
284 | }
285 | ]
286 | }
287 | }
288 | }
289 | }
290 | ```
291 |
292 | Find a child via its `id` and its owners (taxon) `id`.
293 |
294 | ## List Products of a Taxon
295 |
296 | ```shell
297 | curl "https://example.com/api/v2/taxons/1/products"
298 | ```
299 |
300 | ```json
301 | {
302 | "data": [
303 | {
304 | "id": "10",
305 | "type": "spree_products",
306 | "attributes": {
307 | "name": "Spree Ringer T-Shirt",
308 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.",
309 | "slug": "spree-ringer-t-shirt",
310 | "meta_description": null,
311 | "meta_keywords": null,
312 | "store_name": "Whole New Home"
313 | },
314 | "relationships": {
315 | "master": {
316 | "data": {
317 | "type": "spree_variants",
318 | "id": "10"
319 | }
320 | },
321 | "variants": {
322 | "data": []
323 | },
324 | "taxons": {
325 | "data": [
326 | {
327 | "type": "spree_taxons",
328 | "id": "1"
329 | }
330 | ]
331 | },
332 | "option_types": {
333 | "data": [
334 | {
335 | "type": "spree_option_types",
336 | "id": "1"
337 | }
338 | ]
339 | },
340 | "images": {
341 | "data": [
342 | {
343 | "type": "spree_images",
344 | "id": "1"
345 | }
346 | ]
347 | }
348 | }
349 | }
350 | ]
351 | }
352 | ```
353 |
354 | List all of the products that taxon owns via the taxons `id`.
355 |
356 | ## Show Product of a Taxon
357 |
358 | ```shell
359 | curl "https://example.com/api/v2/taxons/1/products/10"
360 | ```
361 |
362 | ```json
363 | {
364 | "data": {
365 | "id": "10",
366 | "type": "spree_products",
367 | "attributes": {
368 | "name": "Spree Ringer T-Shirt",
369 | "description": "Velit nemo odio ducimus nobis non doloremque beatae sunt. Totam quia voluptatum perferendis tempore sed voluptate consequuntur. Sit id corporis autem veritatis reprehenderit.",
370 | "slug": "spree-ringer-t-shirt",
371 | "meta_description": null,
372 | "meta_keywords": null,
373 | "store_name": "Whole New Home"
374 | },
375 | "relationships": {
376 | "master": {
377 | "data": {
378 | "type": "spree_variants",
379 | "id": "10"
380 | }
381 | },
382 | "variants": {
383 | "data": []
384 | },
385 | "taxons": {
386 | "data": [
387 | {
388 | "type": "spree_taxons",
389 | "id": "1"
390 | }
391 | ]
392 | },
393 | "option_types": {
394 | "data": [
395 | {
396 | "type": "spree_option_types",
397 | "id": "1"
398 | }
399 | ]
400 | },
401 | "images": {
402 | "data": [
403 | {
404 | "type": "spree_images",
405 | "id": "1"
406 | }
407 | ]
408 | }
409 | }
410 | }
411 | }
412 | ```
413 |
414 | Fetch a product of taxon via the taxon's `id` and the products `id`.
415 |
--------------------------------------------------------------------------------