├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── app ├── controllers │ └── spree │ │ └── graphql_controller.rb └── models │ ├── solidus_graphql_api │ └── configuration.rb │ └── spree │ └── image_decorator.rb ├── bin └── rails ├── config └── initializers │ └── inflections.rb ├── lib ├── generators │ └── solidus_graphql_api │ │ └── install_generator.rb ├── solidus_graphql_api.rb ├── solidus_graphql_api │ ├── engine.rb │ └── version.rb └── spree │ └── graphql │ ├── domain.rb │ ├── lazy_resolver.rb │ ├── lazy_resolver │ ├── adapters │ │ ├── abstract_adapter.rb │ │ ├── batch_loader │ │ │ └── install_generator_helper.rb │ │ └── batch_loader_adapter.rb │ └── type_helper.rb │ ├── lazy_resolvers.rb │ ├── lazy_resolvers │ ├── base.rb │ └── batch_loader │ │ ├── option_value.rb │ │ ├── product.rb │ │ └── variant.rb │ ├── not_implemented_error.rb │ ├── schema.rb │ └── schema │ ├── inputs │ ├── base_input.rb │ └── ransack_query.rb │ ├── interfaces │ └── base_interface.rb │ ├── payloads │ └── base_payload.rb │ └── types │ ├── base_enum.rb │ ├── base_object.rb │ ├── base_object_node.rb │ ├── base_scalar.rb │ ├── base_union.rb │ ├── collection.rb │ ├── collection_sort_keys.rb │ ├── country_code.rb │ ├── credit_card.rb │ ├── currency_code.rb │ ├── date_time.rb │ ├── decimal.rb │ ├── domain.rb │ ├── html.rb │ ├── image.rb │ ├── image_style.rb │ ├── mailing_address.rb │ ├── money.rb │ ├── mutation.rb │ ├── option_type.rb │ ├── option_value.rb │ ├── payment_settings.rb │ ├── product.rb │ ├── product_collection_sort_keys.rb │ ├── product_sort_keys.rb │ ├── query_root.rb │ ├── shop.rb │ ├── url.rb │ └── variant.rb ├── solidus_graphql_api.gemspec └── spec ├── spec_helper.rb ├── spree └── graphql │ ├── domain_spec.rb │ ├── lazy_resolver_spec.rb │ ├── lazy_resolvers_spec.rb │ └── schema │ └── types │ ├── collection_spec.rb │ ├── country_code_spec.rb │ ├── credit_card_spec.rb │ ├── currency_code_spec.rb │ ├── mailing_address_spec.rb │ ├── mutation_spec.rb │ ├── option_type_spec.rb │ ├── option_value_spec.rb │ ├── payment_settings_spec.rb │ ├── product_collection_sort_keys_spec.rb │ ├── product_sort_keys_spec.rb │ ├── product_spec.rb │ ├── query_root_spec.rb │ ├── shop_spec.rb │ ├── url_spec.rb │ └── variant_spec.rb └── support ├── graphql.rb ├── shared_contexts.rb └── spree.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | /.Gemfile 3 | /spec/dummy 4 | /spec/examples.txt 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | require: rubocop-rspec 4 | 5 | AllCops: 6 | Exclude: 7 | - 'spec/dummy/**/*' 8 | - 'vendor/bundle/**/*' 9 | TargetRubyVersion: 2.3 10 | 11 | Layout/EmptyLineBetweenDefs: 12 | Enabled: false 13 | 14 | # Currently disabled, it conflicts with `rubocop --auto-gen-config` 15 | # Metrics/LineLength: 16 | # Max: 120 17 | # AllowHeredoc: false 18 | 19 | RSpec/FilePath: 20 | CustomTransform: 21 | GraphQL: 'graphql' 22 | 23 | Style/AsciiComments: 24 | Enabled: false 25 | Style/ClassAndModuleChildren: 26 | EnforcedStyle: 'compact' 27 | Style/SpecialGlobalVars: 28 | Exclude: 29 | - 'solidus_graphql_api.gemspec' 30 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2019-05-09 18:27:50 +0200 using RuboCop version 0.66.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | Lint/EmptyWhen: 11 | Exclude: 12 | - 'lib/spree/graphql/schema/types/product_collection_sort_keys.rb' 13 | 14 | # Offense count: 2 15 | # Cop supports --auto-correct. 16 | # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. 17 | Lint/UnusedMethodArgument: 18 | Exclude: 19 | - 'lib/spree/graphql/schema/types/mailing_address.rb' 20 | 21 | # Offense count: 2 22 | Metrics/AbcSize: 23 | Max: 18 24 | 25 | # Offense count: 41 26 | # Configuration parameters: CountComments, ExcludedMethods. 27 | # ExcludedMethods: refine 28 | Metrics/BlockLength: 29 | Max: 686 30 | 31 | # Offense count: 3 32 | # Configuration parameters: CountComments. 33 | Metrics/ClassLength: 34 | Max: 244 35 | 36 | # Offense count: 3 37 | Metrics/CyclomaticComplexity: 38 | Max: 12 39 | 40 | # Offense count: 7 41 | # Configuration parameters: CountComments, ExcludedMethods. 42 | Metrics/MethodLength: 43 | Max: 28 44 | 45 | # Offense count: 3 46 | # Configuration parameters: CountComments. 47 | Metrics/ModuleLength: 48 | Max: 688 49 | 50 | # Offense count: 3 51 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. 52 | # AllowedNames: io, id, to, by, on, in, at, ip, db 53 | Naming/UncommunicativeMethodParamName: 54 | Exclude: 55 | - 'lib/spree/graphql/schema/types/collection_sort_keys.rb' 56 | - 'lib/spree/graphql/schema/types/product_collection_sort_keys.rb' 57 | - 'lib/spree/graphql/schema/types/product_sort_keys.rb' 58 | 59 | # Offense count: 17 60 | # Configuration parameters: CustomIncludeMethods. 61 | RSpec/EmptyExampleGroup: 62 | Exclude: 63 | - 'spec/spree/graphql/schema/types/country_code_spec.rb' 64 | - 'spec/spree/graphql/schema/types/credit_card_spec.rb' 65 | - 'spec/spree/graphql/schema/types/currency_code_spec.rb' 66 | - 'spec/spree/graphql/schema/types/mailing_address_spec.rb' 67 | - 'spec/spree/graphql/schema/types/mutation_spec.rb' 68 | - 'spec/spree/graphql/schema/types/payment_settings_spec.rb' 69 | - 'spec/spree/graphql/schema/types/product_collection_sort_keys_spec.rb' 70 | - 'spec/spree/graphql/schema/types/product_sort_keys_spec.rb' 71 | - 'spec/spree/graphql/schema/types/url_spec.rb' 72 | 73 | # Offense count: 50 74 | RSpec/LetSetup: 75 | Exclude: 76 | - 'spec/spree/graphql/schema/types/country_code_spec.rb' 77 | - 'spec/spree/graphql/schema/types/credit_card_spec.rb' 78 | - 'spec/spree/graphql/schema/types/currency_code_spec.rb' 79 | - 'spec/spree/graphql/schema/types/mailing_address_spec.rb' 80 | - 'spec/spree/graphql/schema/types/payment_settings_spec.rb' 81 | - 'spec/spree/graphql/schema/types/product_collection_sort_keys_spec.rb' 82 | - 'spec/spree/graphql/schema/types/product_sort_keys_spec.rb' 83 | - 'spec/spree/graphql/schema/types/query_root_spec.rb' 84 | - 'spec/spree/graphql/schema/types/url_spec.rb' 85 | 86 | # Offense count: 1 87 | # Configuration parameters: . 88 | # SupportedStyles: have_received, receive 89 | RSpec/MessageSpies: 90 | EnforcedStyle: receive 91 | 92 | # Offense count: 4 93 | # Configuration parameters: AggregateFailuresByDefault. 94 | RSpec/MultipleExpectations: 95 | Max: 2 96 | 97 | # Offense count: 1 98 | # Cop supports --auto-correct. 99 | # Configuration parameters: Keywords. 100 | # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW 101 | Style/CommentAnnotation: 102 | Exclude: 103 | - 'lib/spree/graphql/schema/types/product_collection_sort_keys.rb' 104 | 105 | # Offense count: 34 106 | Style/Documentation: 107 | Enabled: false 108 | 109 | # Offense count: 344 110 | # Cop supports --auto-correct. 111 | # Configuration parameters: PreferredDelimiters. 112 | Style/PercentLiteralDelimiters: 113 | Enabled: false 114 | 115 | # Offense count: 506 116 | # Cop supports --auto-correct. 117 | Style/UnneededPercentQ: 118 | Enabled: false 119 | 120 | # Offense count: 137 121 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 122 | # URISchemes: http, https 123 | Metrics/LineLength: 124 | Max: 1236 125 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.4 4 | - 2.5 5 | - 2.6 6 | env: 7 | global: 8 | - LAZY_RESOLVER=batch-loader 9 | matrix: 10 | - SOLIDUS_BRANCH=v2.7 DB=postgres 11 | - SOLIDUS_BRANCH=v2.8 DB=postgres 12 | - SOLIDUS_BRANCH=master DB=postgres 13 | - SOLIDUS_BRANCH=v2.7 DB=mysql 14 | - SOLIDUS_BRANCH=v2.8 DB=mysql 15 | - SOLIDUS_BRANCH=master DB=mysql 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | git_source(:github) { |name| "https://github.com/#{name}.git" } 6 | 7 | solidus_branch = ENV.fetch('SOLIDUS_BRANCH', 'master') 8 | 9 | gem 'solidus', github: 'solidusio/solidus', branch: solidus_branch 10 | gem 'solidus_auth_devise' 11 | 12 | case ENV['DB'] 13 | when 'mysql' 14 | gem 'mysql2', '~> 0.4.10' 15 | when 'postgres' 16 | gem 'pg', '~> 0.21' 17 | end 18 | 19 | case ENV['LAZY_RESOLVER'] 20 | when 'batch-loader' 21 | gem 'batch-loader', '~> 1.4.0' 22 | end 23 | 24 | gemspec 25 | 26 | local_gemfile = File.expand_path('.Gemfile', __dir__) 27 | instance_eval File.read local_gemfile if File.exist? local_gemfile 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 [name of plugin creator] 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 Spree nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidus GraphQL API 2 | #DEPRECATED 3 | ### 4 | This was an experimental approach and the community has decided to take a different direction. 5 | 6 | 7 | See ->https://github.com/nebulab/solidus_graphql_api-1 8 | 9 | 10 | 11 | [![Build Status](https://api.travis-ci.org/solidusio-contrib/solidus_graphql_api.svg?branch=master)](https://travis-ci.org/solidusio-contrib/solidus_graphql_api) 12 | 13 | NOTE: This is a work in progress. Please join the Solidus Slack #graphql channel at [https://solidusio.slack.com/](https://solidusio.slack.com/) if you are interested. 14 | 15 | The complete list of Solidus GraphQL calls and their implementation status can be found below, towards the end of the document. 16 | 17 | 18 | ## Installation 19 | 20 | 1) Add solidus_graphql_api to your `Gemfile`. The complete block added to `Gemfile` could look like this: 21 | 22 | ```ruby 23 | gem 'solidus' 24 | gem 'solidus_auth_devise' 25 | gem 'solidus_graphql_api', github: 'solidusio-contrib/solidus_graphql_api' 26 | 27 | gem 'graphiql-rails', group: :development 28 | ``` 29 | 30 | 2) Run `bundle`: 31 | 32 | ```sh 33 | bundle 34 | ``` 35 | 36 | 3) Run the extension generator and run `bundle` again as it could insert additional dependencies: 37 | 38 | ```sh 39 | bundle exec rails g solidus_graphql_api:install 40 | bundle 41 | ``` 42 | 43 | NOTE: If this is your new Rails + Solidus application, please don't forget to run the Solidus installation steps, which at a minimum consist of adding the database `username`, `password`, and `host` in `config/database.yml`'s `default` block and running `bundle exec rails g spree:install`. 44 | 45 | 4) Add routes to your application's `config/routes.rb` to serve GraphQL and optionally also GraphiQL queries in development mode: 46 | 47 | ```ruby 48 | post :graphql, to: 'spree/graphql#execute' 49 | 50 | if Rails.env.development? 51 | mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' 52 | end 53 | ``` 54 | 55 | ## Testing and Usage 56 | 57 | To run the whole app, run your usual `bundle exec rails s`. 58 | 59 | To perform a test GraphQL query, you can use any of the following methods: 60 | 61 | ```shell 62 | # Using HTTP POST (arguments passed in JSON body): 63 | curl http://localhost:3000/graphql -H content-type:application/json -d '{ "query": " { shop { name } }" }' 64 | wget http://localhost:3000/graphql --header content-type:application/json -qO- --post-data '{ "query": " { shop { name } }" }' 65 | echo '{ "query": "{ shop { name } }" }' | POST -c application/json http://localhost:3000/graphql 66 | POST -c application/json http://localhost:3000/graphql <<< '{ "query": "{ shop { name } }" }' 67 | 68 | # Using HTTP POST with query strings (also works, may be simpler to write during testing): 69 | curl http://localhost:3000/graphql -d 'query={ shop { name } }' 70 | wget http://localhost:3000/graphql -qO- --post-data 'query={ shop { name } }' 71 | echo 'query={ shop { name } }' | POST http://localhost:3000/graphql 72 | POST http://localhost:3000/graphql <<< 'query={ shop { name } }' 73 | ``` 74 | 75 | Assuming you are running this on a default Rails/Solidus installation with sample data, the response should look like this: 76 | 77 | ```json 78 | {"data":{"shop":{"name":"Sample Store"}}} 79 | ``` 80 | 81 | To open GraphiQL browser, visit [http://localhost:3000/graphiql](http://localhost:3000/graphiql) in your browser while in development environment. 82 | 83 | To see what queries can currently be specified, see GraphiQL's web interface. 84 | 85 | Please note that all GraphQL API fields which do not yet contain an actual implementation will return a `NotImplementedError`. For all such fields that you attempt to use but they aren't available, please submit an issue or report them in Solidus Slack channel #graphql. That will help prioritize work and ensure we begin with the implementation on the next working day. 86 | 87 | ## How to Contribute 88 | 89 | In solidus_graphql_api, GraphQL files are located in directory `lib/spree/graphql/`. And additionally, there is also top-level directory `spec/` with all the specs. 90 | 91 | To e.g. implement `Order.totalTax`, you would do as follows: 92 | 93 | 1. Add the schema definition and the implementation code in file `./lib/spree/graphql/schema/types/order.rb` 94 | 1. Add specs in file `./spec/spree/graphql/schema/types/order_spec.rb` 95 | 1. After specs pass, update `README.md` to mark new GraphQL calls as implemented and submit a PR 96 | 97 | Notes / additional help on adding code and specs follow: 98 | 99 | ### Adding GraphQL Field Implementations 100 | 101 | As you might know, every GraphQL field returns either an object for more querying on it, or a leaf value. 102 | 103 | For example, a query such as `{ shop { primaryDomain { url }}}` expects `QueryRoot.shop` to return an instance of `Shop`, `shop.primaryDomain` to return an instance of `Domain`, and finally `primaryDomain.url` to return the leaf value containing the URL. If it happens that non-leaf fields return a leaf value (or leaf fields return a non-leaf value), an error is raised. 104 | 105 | In the simplest cases, especially those in which the created GraphQL types have direct equivalents in Solidus core, the implementations can be trivial. For example, for `{ shop { name }}`, `shop` may just return an instance of `Spree::Store` and `name` may just call `name` on that instance. 106 | 107 | Sometimes, however, types do not have direct mappings. For example, GraphQL type `Domain` has fields `host`, `sslEnabled`, and `url`. It does not have a direct equivalent in Solidus, and it is not backed by a database. However, we know we can put this information together; `host` by calling `Spree::Store#url`, `sslEnabled` by querying `Rails.configuration.force_ssl`, and `url` by concatenating the protocol and host together. The only thing left to do then is to decide where to put this code. 108 | 109 | Option 1: for insignificant, disposable types, we can add implementation directly in the GraphQL method for `shop.primaryDomain` (`./lib/spree/graphql/schema/types/shop.rb`): 110 | 111 | ``` 112 | require 'ostruct' 113 | def primaryDomain 114 | ssl = Rails.configuration.force_ssl 115 | url_prefix = ssl ? 'https://' : 'http://' 116 | 117 | OpenStruct.new( 118 | host: object.url, 119 | ssl_enabled: ssl, 120 | url: url_prefix + object.url 121 | ) 122 | end 123 | ``` 124 | 125 | Option 2: for more important types which may be created and have additional methods defined on them, we can create a Ruby class (`./lib/spree/graphql/domain.rb`) and instantiate it from `primaryDomain`: 126 | 127 | ``` 128 | class Spree::GraphQL::Domain 129 | attr_reader :host, :ssl_enabled, :url 130 | 131 | def initialize(host, ssl_enabled, url = nil) 132 | @host = host 133 | @ssl_enabled = ssl_enabled 134 | @url = url || ((@ssl_enabled ? 'https://' : 'http://') + @host) 135 | end 136 | end 137 | 138 | ``` 139 | 140 | (If a type is backed by a database and represents a model, `app/models/` would be used instead of `lib/spree/graphql/`.) 141 | 142 | ### Adding Corresponding specs 143 | 144 | [Spec helpers](spec/support/graphql.rb) are provided to run a typical GraphQL spec: these helpers are automatically defined for specs located within `spec/spree/graphql/schema/`. 145 | 146 | A typical spec is structured as the following: 147 | 148 | ```ruby 149 | let(:ctx) { { context_key: 'contextValue' } } 150 | let(:variables) { { var1: 'var1', var2: 'var2' } } 151 | let(:query) { '{ someQuery }' } 152 | let(:result) { { someResultKey: 'someResultValue' } } 153 | 154 | it 'succeeds' do 155 | execute 156 | expect(response_hash).to eq(result_hash) 157 | end 158 | ``` 159 | 160 | The header of each spec defines the GraphQL query and expected result, followed by an `it` block which actually runs the query and comparison. 161 | 162 | The command to execute queries is just `execute`. It runs `Schema.execute()` with defaulting to `query()`, `context()` and `variables()` unless these params are manually provided. 163 | 164 | After the query is executed, the response is available through `response()` as usual. 165 | 166 | For it, there are helpers defined — `response_json()` to get JSON string of the response, and `response_hash()` to get Ruby hash of the response. 167 | 168 | For the expected result, there are analogously-named helpers defined on it: `result_json()` and `result_hash()`. 169 | 170 | So, to match the response to the expected result, one can use either of the following methods, regardless of the format in which the response and result were originally given: 171 | 172 | ``` 173 | expect(response_json).to eq(result_json) 174 | 175 | expect(response_hash).to eq(result_hash) 176 | ``` 177 | 178 | For helper types and classes which are not directly accessible from the outside (such as type `Domain`), it is not necessary to make GraphQL calls but classes themselves can be tested. 179 | 180 | Please see the spec files for more examples. Good places to check first are the specs for `Product`, `primaryDomain`, `MailingAddress` and `CreditCard` as they each use a different method to carry out the specs. 181 | 182 | ### Test Suite 183 | 184 | To run solidus_graphql_api specs, invoke `rake`. `rake` will default to building a dummy app in `spec/dummy`, and finally running the specs. 185 | 186 | ```shell 187 | bundle 188 | bundle exec rake 189 | 190 | # Or individually: 191 | bundle exec rake test_app 192 | bundle exec rake spec 193 | 194 | # Or using rspec for tests directly: 195 | bundle exec rspec 196 | bundle exec rspec spec/graphql/types/product_spec.rb 197 | ``` 198 | 199 | ## State of Implementation 200 | 201 | - [x] Collection.description 202 | - [x] Collection.handle 203 | - [x] Collection.id 204 | - [x] Collection.products(first, after, last, before, reverse, sortKey) 205 | - [x] Collection.title 206 | - [x] Collection.updatedAt 207 | - [x] CreditCard.brand 208 | - [x] CreditCard.expiryMonth 209 | - [x] CreditCard.expiryYear 210 | - [x] CreditCard.firstName 211 | - [x] CreditCard.lastDigits 212 | - [x] CreditCard.lastName 213 | - [x] CreditCard.maskedNumber 214 | - [x] Domain.host 215 | - [x] Domain.sslEnabled 216 | - [x] Domain.url 217 | - [x] MailingAddress.address1 218 | - [x] MailingAddress.address2 219 | - [x] MailingAddress.city 220 | - [x] MailingAddress.company 221 | - [x] MailingAddress.country 222 | - [x] MailingAddress.countryCodeV2 223 | - [x] MailingAddress.firstName 224 | - [x] MailingAddress.formattedArea 225 | - [x] MailingAddress.id 226 | - [x] MailingAddress.lastName 227 | - [x] MailingAddress.name 228 | - [x] MailingAddress.phone 229 | - [x] MailingAddress.province 230 | - [x] MailingAddress.provinceCode 231 | - [x] MailingAddress.zip 232 | - [x] Node.id 233 | - [x] PageInfo.hasNextPage 234 | - [x] PageInfo.hasPreviousPage 235 | - [x] PaymentSettings.countryCode 236 | - [x] PaymentSettings.currencyCode 237 | - [x] Product.createdAt 238 | - [x] Product.description 239 | - [x] Product.handle 240 | - [x] Product.id 241 | - [x] Product.publishedAt 242 | - [x] Product.title 243 | - [x] Product.updatedAt 244 | - [x] ProductVariant.id 245 | - [x] ProductVariant.product 246 | - [x] Product.variants(first, after, last, before, reverse, sortKey) 247 | - [x] ProductVariant.sku 248 | - [x] ProductVariant.title 249 | - [x] ProductVariant.weight 250 | - [x] QueryRoot.node(id) 251 | - [x] QueryRoot.nodes(ids) 252 | - [x] QueryRoot.shop 253 | - [x] Shop.collectionByHandle(handle) - (works only for Spree taxons, not top-level taxonomies) 254 | - [x] Shop.collections(first, after, last, before, reverse, sortKey, query) - (`query` not supported yet; returns top-level Spree taxonomies) 255 | - [x] Shop.moneyFormat 256 | - [x] Shop.paymentSettings 257 | - [x] Shop.description 258 | - [x] Shop.name 259 | - [x] Shop.primaryDomain 260 | - [x] Shop.productByHandle(handle) 261 | - [x] Shop.products(first, after, last, before, reverse, sortKey, query) - (`query` not supported yet) 262 | - [x] Shop.shipsToCountries 263 | 264 | 265 | ## Implementation Notes 266 | 267 | This section contains notes related to implementation or behavior of some GraphQL fields: 268 | 269 | 1. Collections and Products. The upstream API has a concept of Collections which are nested product categories. In Solidus, these are implemented through top-level Taxonomies and contained Taxons. Taxonomies have a small number of fields, and cannot be nested. Taxons have a longer list of existing fields, can be nested, and more closely match the concept of Collections. Solidus' GraphQL implementation for Collections emulates them by transparently returning Taxonomies at the top level and Taxons at lower levels. However, more work is needed to make it fully transparent, and `collectionByHandle()` should be improved too. For more discussion about the approach itself, see https://github.com/solidusio-contrib/solidus_graphql_api/issues/25. 270 | 1. Domain.sslEnabled. This field returns value of `Rails.configuration.force_ssl` and so it does not accurately report whether SSL is "enabled", but whether it is "forced". 271 | 1. PaymentSettings.countryCode. Since the default `Spree::Store` model does not have a field for the store's address and country, the value of this field is coming from `Spree::Store#cart_tax_country_iso`, which in turn is the default country whose tax should be applied. Comments as to whether this is a good way to produce this value are welcome. 272 | 1. PaymentSettings.currencyCode. The value of this field is coming from `Spree::Store#default_currency` and so it does not necessarily represent the _only_ currency accepted, but just the _default_ one. 273 | 1. Shop.moneyFormat. There is no equivalent of this field in `Solidus` or `Spree::Money`, so the value is produced by running the formatting method on an example amount, and then guessing the money format template from that. The current implementation seems like it should cover a good number of use cases. However, check this value yourself to ensure it is correct in your setup. 274 | 1. Shop.shipsToCountries. In Solidus, this field does not exist anywhere out-of-the-box, but support for this is provided. To make this field return a meaningful result, one should create a Zone in Solidus Admin and add chosen countries in it. Then, in Solidus configuration, the setting `checkout_zone` should be set to name of that zone. 275 | 276 | ## TODO 277 | 278 | The primary TODOs related to this extension are: 279 | 280 | 1. Add authentication (#14) 281 | 1. Add authorization (#15) 282 | 1. All other open issues (https://github.com/solidusio-contrib/solidus_graphql_api/issues) 283 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | 5 | Bundler::GemHelper.install_tasks 6 | 7 | require 'spree/testing_support/extension_rake' 8 | require 'rubocop/rake_task' 9 | require 'rspec/core/rake_task' 10 | 11 | RSpec::Core::RakeTask.new(:spec) 12 | 13 | RuboCop::RakeTask.new 14 | 15 | task default: %i(first_run rubocop spec) 16 | 17 | task :first_run do 18 | if Dir['spec/dummy'].empty? 19 | Rake::Task[:test_app].invoke 20 | Dir.chdir('../../') 21 | end 22 | end 23 | 24 | desc 'Shorthand to generate dummy app for testing the extension with defaults' 25 | task :test_app do 26 | ENV['LIB_NAME'] = 'solidus_graphql_api' 27 | Rake::Task['extension:test_app'].invoke 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/spree/graphql_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQLController < Spree::Api::BaseController 4 | skip_before_action :verify_authenticity_token 5 | 6 | # Shut off authentication for now 7 | def requires_authentication? 8 | # Spree::Api::Config[:requires_authentication] 9 | false 10 | end 11 | 12 | def execute 13 | variables = ensure_hash(params[:variables]) 14 | query = params[:query] 15 | operation_name = params[:operationName] 16 | context = { 17 | current_spree_user: current_spree_user, 18 | current_store: current_store, 19 | helpers: helpers 20 | } 21 | result = Spree::GraphQL::Schema.execute(query, variables: variables, context: context, operation_name: operation_name) 22 | render json: result 23 | rescue StandardError => exception 24 | raise exception unless Rails.env.development? 25 | 26 | handle_error_in_development exception 27 | end 28 | 29 | private 30 | 31 | # Handle form data, JSON body, or a blank value 32 | def ensure_hash(ambiguous_param) 33 | case ambiguous_param 34 | when String 35 | if ambiguous_param.present? 36 | ensure_hash(JSON.parse(ambiguous_param)) 37 | else 38 | {} 39 | end 40 | when Hash, ActionController::Parameters 41 | ambiguous_param 42 | when nil 43 | {} 44 | else 45 | raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" 46 | end 47 | end 48 | 49 | def handle_error_in_development(exception) 50 | logger.error exception.message 51 | logger.error exception.backtrace.join("\n") 52 | 53 | render json: { error: { message: exception.message, backtrace: exception.backtrace }, data: {} }, status: 500 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/models/solidus_graphql_api/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SolidusGraphqlApi::Configuration < Spree::Preferences::Configuration 4 | preference :lazy_resolver_adapter_class_name, 5 | :string, 6 | default: 'Spree::GraphQL::LazyResolver::Adapters::BatchLoaderAdapter' 7 | end 8 | -------------------------------------------------------------------------------- /app/models/spree/image_decorator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi::ImageDecorator 4 | def self.prepended(base) 5 | base.whitelisted_ransackable_attributes = (base.whitelisted_ransackable_attributes || []) | %w[position] 6 | end 7 | 8 | Spree::Image.prepend self 9 | end 10 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This command will automatically be run when you run "rails" with Rails gems 5 | # installed from the root of your application. 6 | 7 | ENGINE_ROOT = File.expand_path('..', __dir__) 8 | ENGINE_PATH = File.expand_path('../lib/solidus_graphql_api/engine', __dir__) 9 | APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__) 10 | 11 | # Set up gems listed in the Gemfile. 12 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 13 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 14 | 15 | require 'rails/all' 16 | require 'rails/engine/commands' 17 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActiveSupport::Inflector.inflections do |inflect| 4 | inflect.acronym 'GraphQL' 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/solidus_graphql_api/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SolidusGraphqlApi::InstallGenerator < Rails::Generators::Base 4 | include Spree::GraphQL::LazyResolver.install_generator_helper 5 | 6 | def add_lazy_resolvers_adapter_dependencies 7 | super 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'graphql' 4 | require 'solidus_core' 5 | 6 | module SolidusGraphqlApi; end 7 | 8 | require 'solidus_graphql_api/engine' 9 | require 'solidus_graphql_api/version' 10 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SolidusGraphqlApi::Engine < Rails::Engine 4 | require 'spree/core' 5 | isolate_namespace Spree 6 | engine_name 'solidus_graphql_api' 7 | 8 | # use rspec for tests 9 | config.generators do |g| 10 | g.test_framework :rspec 11 | end 12 | 13 | config.eager_load_paths << File.expand_path('..', __dir__) 14 | 15 | initializer 'solidus_graphql_api.environment', before: :load_config_initializers do |_app| 16 | SolidusGraphqlApi::Config = SolidusGraphqlApi::Configuration.new 17 | end 18 | 19 | def self.activate 20 | Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c| 21 | Rails.configuration.cache_classes ? require(c) : load(c) 22 | end 23 | end 24 | 25 | config.to_prepare(&method(:activate).to_proc) 26 | end 27 | -------------------------------------------------------------------------------- /lib/solidus_graphql_api/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SolidusGraphqlApi 4 | VERSION = '0.0.1' 5 | end 6 | -------------------------------------------------------------------------------- /lib/spree/graphql/domain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Domain 4 | attr_reader :host, :ssl_enabled, :url 5 | 6 | def initialize(host, ssl_enabled, url = nil) 7 | @host = host 8 | @ssl_enabled = ssl_enabled 9 | @url = url || ((@ssl_enabled ? 'https://' : 'http://') + @host) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::LazyResolver 4 | ADAPTER = SolidusGraphqlApi::Config.lazy_resolver_adapter_class_name.constantize 5 | private_constant :ADAPTER 6 | 7 | METHODS_DELEGATED_TO_ADAPTER = %i[ 8 | activate 9 | adapter_namespace 10 | clear_cache 11 | install_generator_helper 12 | lookup_resolver_class 13 | require_dependencies 14 | resolvers_namespace 15 | ].freeze 16 | 17 | class << self 18 | delegate(*METHODS_DELEGATED_TO_ADAPTER, to: :adapter) 19 | end 20 | 21 | def self.adapter 22 | ADAPTER 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolver/adapters/abstract_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolver::Adapters::AbstractAdapter 4 | INSTALL_GENERATOR_HELPER_MODULE_NAME = 'InstallGeneratorHelper' 5 | private_constant :INSTALL_GENERATOR_HELPER_MODULE_NAME 6 | 7 | def self.activate(_schema) 8 | raise NotImplementedError 9 | end 10 | 11 | def self.adapter_namespace 12 | to_s.sub(/Adapter\z/, '').constantize 13 | end 14 | 15 | def self.clear_cache 16 | raise NotImplementedError 17 | end 18 | 19 | def self.install_generator_helper 20 | adapter_namespace.const_get INSTALL_GENERATOR_HELPER_MODULE_NAME 21 | end 22 | 23 | def self.lookup_resolver_class(graphql_type_class) 24 | resolvers_namespace.const_get graphql_type_class.to_s.demodulize 25 | end 26 | 27 | def self.require_dependencies 28 | raise NotImplementedError 29 | end 30 | 31 | def self.resolvers_namespace 32 | raise NotImplementedError 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolver/adapters/batch_loader/install_generator_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::LazyResolver::Adapters::BatchLoader::InstallGeneratorHelper 4 | def add_lazy_resolvers_adapter_dependencies 5 | gem 'batch-loader', '~> 1.4.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolver/adapters/batch_loader_adapter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolver::Adapters::BatchLoaderAdapter < 4 | Spree::GraphQL::LazyResolver::Adapters::AbstractAdapter 5 | def self.activate(schema) 6 | require_dependencies 7 | 8 | schema.use ::BatchLoader::GraphQL 9 | end 10 | 11 | def self.clear_cache 12 | ::BatchLoader::Executor.clear_current 13 | end 14 | 15 | def self.require_dependencies 16 | gem 'batch-loader', '~> 1.4.0' 17 | end 18 | 19 | def self.resolvers_namespace 20 | Spree::GraphQL::LazyResolvers::BatchLoader 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolver/type_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::LazyResolver::TypeHelper 4 | protected 5 | 6 | def lazy_resolver 7 | Spree::GraphQL::LazyResolvers.for(self.class).new(object, context) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolvers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::LazyResolvers 4 | def self.for(graphql_type_class) 5 | Spree::GraphQL::LazyResolver.lookup_resolver_class graphql_type_class 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolvers/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolvers::Base 4 | attr_reader :object, :context 5 | 6 | def initialize(object, context) 7 | @object = object 8 | @context = context 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolvers/batch_loader/option_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolvers::BatchLoader::OptionValue < Spree::GraphQL::LazyResolvers::Base 4 | def option_type 5 | ::BatchLoader::GraphQL.for(object.option_type_id).batch do |option_type_ids, loader| 6 | option_type_batch(option_type_ids).each { |option_type| loader.call(option_type.id, option_type) } 7 | end 8 | end 9 | 10 | private 11 | 12 | def option_type_batch(option_type_ids) 13 | Spree::OptionType.where(id: option_type_ids) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolvers/batch_loader/product.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolvers::BatchLoader::Product < Spree::GraphQL::LazyResolvers::Base 4 | def master_variant 5 | ::BatchLoader::GraphQL.for(object.id).batch do |product_ids, loader| 6 | master_variant_batch(product_ids).each { |variant| loader.call(variant.product_id, variant) } 7 | end 8 | end 9 | 10 | def variants(including_master:, query:) 11 | query = Spree::GraphQL::Schema::Inputs::RansackQuery.queries_to_ransack_query(query) 12 | 13 | ::BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |product_ids, loader| 14 | variants_batch(product_ids, query, including_master).each do |variant| 15 | loader.call(variant.product_id) { |memo| memo << variant } 16 | end 17 | end 18 | end 19 | 20 | private 21 | 22 | def master_variant_batch(product_ids) 23 | Spree::Variant.where(product_id: product_ids).where(is_master: true) 24 | end 25 | 26 | def variants_batch(product_ids, query, including_master) 27 | variants = Spree::Variant.where(product_id: product_ids).ransack(query).result 28 | variants = variants.where(is_master: false) unless including_master 29 | variants 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/spree/graphql/lazy_resolvers/batch_loader/variant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::LazyResolvers::BatchLoader::Variant < Spree::GraphQL::LazyResolvers::Base 4 | def images(query:) 5 | query = Spree::GraphQL::Schema::Inputs::RansackQuery.queries_to_ransack_query(query) 6 | 7 | ::BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |variant_ids, loader| 8 | images_batch(variant_ids, query).each do |image| 9 | loader.call(image.viewable_id) { |memo| memo << image } 10 | end 11 | end 12 | end 13 | 14 | def option_values 15 | ::BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |variant_ids, loader| 16 | option_values_batch(variant_ids).each do |option_values_variant| 17 | loader.call(option_values_variant.variant_id) { |memo| memo << option_values_variant.option_value } 18 | end 19 | end 20 | end 21 | 22 | def price 23 | pricing_options = context[:helpers].current_pricing_options 24 | 25 | ::BatchLoader::GraphQL.for(object.id).batch(default_value: nil) do |variant_ids, loader| 26 | price_batch(variant_ids).each do |price| 27 | loader.call(price.variant_id) do |memo| 28 | return memo if memo 29 | 30 | price.try! :money if current_price?(price, pricing_options) 31 | end 32 | end 33 | end 34 | end 35 | 36 | def prices 37 | ::BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |variant_ids, loader| 38 | prices_batch(variant_ids).each do |price| 39 | loader.call(price.variant_id) { |memo| memo << price.money } 40 | end 41 | end 42 | end 43 | 44 | def product 45 | ::BatchLoader::GraphQL.for(object.product_id).batch do |product_ids, loader| 46 | product_batch(product_ids).each { |product| loader.call(product.id, product) } 47 | end 48 | end 49 | 50 | private 51 | 52 | def images_batch(variant_ids, query) 53 | Spree::Image.where(viewable_type: object.class.to_s, viewable_id: variant_ids).ransack(query).result 54 | end 55 | 56 | def option_values_batch(variant_ids) 57 | Spree::OptionValuesVariant 58 | .includes(:variant, :option_value) 59 | .references(:option_value) 60 | .where(variant_id: variant_ids) 61 | .order(Spree::OptionValue.arel_table[:position].asc) 62 | end 63 | 64 | def price_batch(variant_ids) 65 | prices_batch(variant_ids) 66 | end 67 | 68 | def prices_batch(variant_ids) 69 | Spree::Price.currently_valid.where(variant_id: variant_ids) 70 | end 71 | 72 | def product_batch(product_ids) 73 | Spree::Product.where(id: product_ids) 74 | end 75 | 76 | def current_price?(price, pricing_options) 77 | (price.country_iso == pricing_options.desired_attributes[:country_iso] || price.country_iso.nil?) && 78 | price.currency == pricing_options.desired_attributes[:currency] 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/spree/graphql/not_implemented_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::NotImplementedError < ::GraphQL::ExecutionError 4 | def initialize 5 | super 'Not implemented yet' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'graphql' 4 | require 'ostruct' 5 | 6 | class Spree::GraphQL::Schema < ::GraphQL::Schema 7 | query ::Spree::GraphQL::Schema::Types::QueryRoot 8 | mutation ::Spree::GraphQL::Schema::Types::Mutation 9 | 10 | Spree::GraphQL::LazyResolver.activate(self) 11 | 12 | def self.id_from_object(object, _type_definition = nil, _query_context = nil) 13 | ::GraphQL::Schema::UniqueWithinType.encode(object.class.name, object.id) 14 | end 15 | 16 | def self.object_from_id(id, _query_context = nil) 17 | class_name, item_id = ::GraphQL::Schema::UniqueWithinType.decode(id) 18 | ::Object.const_get(class_name).find(item_id) 19 | end 20 | 21 | def self.resolve_type(_type, obj, _ctx) 22 | case obj 23 | when ::Spree::Product 24 | ::Spree::GraphQL::Schema::Types::Product 25 | when ::Spree::Variant 26 | ::Spree::GraphQL::Schema::Types::ProductVariant 27 | when ::Spree::Taxonomy 28 | ::Spree::GraphQL::Schema::Types::Collection 29 | when ::Spree::Taxon 30 | ::Spree::GraphQL::Schema::Types::Collection 31 | when ::Spree::Address 32 | ::Spree::GraphQL::Schema::Types::MailingAddress 33 | else 34 | raise("Unexpected object: #{obj}") 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/inputs/base_input.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Inputs::BaseInput < GraphQL::Schema::InputObject 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/inputs/ransack_query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Inputs::RansackQuery < Spree::GraphQL::Schema::Inputs::BaseInput 4 | description <<~MD.chomp 5 | Query command matching the 6 | [Ransack](https://github.com/activerecord-hackery/ransack#search-matchers) syntax. 7 | 8 | ### Examples 9 | 10 | ```graphql 11 | query productsList($query: [RansackQuery!]) { 12 | products(query: $query) { 13 | nodes { 14 | id 15 | } 16 | } 17 | } 18 | 19 | # Sort by ID, in descending order 20 | { 21 | "query": [ 22 | { 23 | "key": "s", 24 | "value": "id desc" 25 | } 26 | ] 27 | } 28 | 29 | # Match products whose name contains "t-shirt" 30 | # or whose any variants’ SKU is equal to "00016" 31 | { 32 | "query": [ 33 | { 34 | "key": "name_cont", 35 | "value": "t-shirt" 36 | }, 37 | { 38 | "key": "variants_including_master_sku_eq", 39 | "value": "ROR-00016" 40 | }, 41 | { 42 | "key": "m", 43 | "value": "or" 44 | } 45 | ] 46 | } 47 | ``` 48 | MD 49 | 50 | argument :key, ::GraphQL::Types::String, required: true 51 | argument :value, ::GraphQL::Types::String, required: true 52 | 53 | def self.queries_to_ransack_query(queries) 54 | queries.reduce({}) { |memo, query| memo.update query.to_ransack_query } 55 | end 56 | 57 | def to_ransack_query 58 | { key => value } 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/interfaces/base_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::Schema::Interfaces::BaseInterface 4 | include ::GraphQL::Schema::Interface 5 | end 6 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/payloads/base_payload.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Payloads::BasePayload < GraphQL::Schema::Object 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/base_enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::BaseEnum < GraphQL::Schema::Enum 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/base_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::BaseObject < GraphQL::Schema::Object 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/base_object_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::BaseObjectNode < GraphQL::Schema::Object 4 | global_id_field :id 5 | implements ::GraphQL::Relay::Node.interface 6 | end 7 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/base_scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::BaseScalar < GraphQL::Schema::Scalar 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/base_union.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::BaseUnion < GraphQL::Schema::Union 4 | end 5 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/collection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Collection < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | graphql_name 'Collection' 5 | 6 | description %q{A collection represents a grouping of products that a shop owner can create to organize them or make their shops easier to browse.} 7 | 8 | field :description, ::GraphQL::Types::String, null: false do 9 | description %q{Stripped description of the collection.} 10 | end 11 | def description 12 | text = object.is_a?(Spree::Taxon) ? object.description : object.name 13 | text.to_s.strip 14 | end 15 | 16 | field :handle, ::GraphQL::Types::String, null: false do 17 | description %q{A human-friendly unique string for the collection automatically generated from its title. 18 | Limit of 255 characters. 19 | } 20 | end 21 | def handle 22 | object.is_a?(Spree::Taxon) ? object.permalink : nil 23 | end 24 | 25 | field :products, ::Spree::GraphQL::Schema::Types::Product.connection_type, null: false do 26 | description %q{List of products in the collection.} 27 | argument :reverse, ::GraphQL::Types::Boolean, required: false, default_value: false, description: %q{Reverse the order of the underlying list.} 28 | argument :sort_key, ::Spree::GraphQL::Schema::Types::ProductCollectionSortKeys, required: false, default_value: 'COLLECTION_DEFAULT', description: %q{Sort the underlying list by the given key.} 29 | end 30 | def products(reverse:, sort_key:) 31 | if object.is_a?(Spree::Taxon) 32 | ::Spree::GraphQL::Schema::Types::ProductCollectionSortKeys.apply!( 33 | object.products, 34 | reverse: reverse, 35 | sort_key: sort_key 36 | ) 37 | else 38 | [] 39 | end 40 | end 41 | 42 | field :title, ::GraphQL::Types::String, null: false do 43 | description %q{The collection’s name. Limit of 255 characters.} 44 | end 45 | def title 46 | object.name 47 | end 48 | 49 | field :updated_at, ::Spree::GraphQL::Schema::Types::DateTime, null: false do 50 | description %q{The date and time when the collection was last modified.} 51 | end 52 | def updated_at 53 | object.updated_at 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/collection_sort_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::CollectionSortKeys < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'CollectionSortKeys' 5 | 6 | description %q{The set of valid sort keys for the collections query.} 7 | 8 | value 'TITLE', %q{Sort by the `title` value.} 9 | value 'UPDATED_AT', %q{Sort by the `updated_at` value.} 10 | value 'ID', %q{Sort by the `id` value.} 11 | value 'RELEVANCE', %q{During a search (i.e. when the `query` parameter has been specified on the connection) this sorts the 12 | results by relevance to the search term(s). When no search query is specified, this sort key is not 13 | deterministic and should not be used. 14 | } 15 | 16 | def self.apply!(r, **args) 17 | if args[:sort_key] 18 | r.reorder!( 19 | case args[:sort_key] 20 | when 'TITLE' 21 | :name 22 | when 'UPDATED_AT' 23 | :updated_at 24 | when 'ID' 25 | :id 26 | when 'RELEVANCE' 27 | raise ::Spree::GraphQL::NotImplementedError 28 | else 29 | raise ::Spree::GraphQL::NotImplementedError 30 | end 31 | ) 32 | end 33 | r.reverse_order! if args[:reverse] 34 | r 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/country_code.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::CountryCode < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'CountryCode' 5 | description %q{ISO 3166-1 alpha-2 country codes with some differences.} 6 | value 'AF', %q{Afghanistan.} 7 | value 'AX', %q{Aland Islands.} 8 | value 'AL', %q{Albania.} 9 | value 'DZ', %q{Algeria.} 10 | value 'AD', %q{Andorra.} 11 | value 'AO', %q{Angola.} 12 | value 'AI', %q{Anguilla.} 13 | value 'AG', %q{Antigua And Barbuda.} 14 | value 'AR', %q{Argentina.} 15 | value 'AM', %q{Armenia.} 16 | value 'AW', %q{Aruba.} 17 | value 'AU', %q{Australia.} 18 | value 'AT', %q{Austria.} 19 | value 'AZ', %q{Azerbaijan.} 20 | value 'BS', %q{Bahamas.} 21 | value 'BH', %q{Bahrain.} 22 | value 'BD', %q{Bangladesh.} 23 | value 'BB', %q{Barbados.} 24 | value 'BY', %q{Belarus.} 25 | value 'BE', %q{Belgium.} 26 | value 'BZ', %q{Belize.} 27 | value 'BJ', %q{Benin.} 28 | value 'BM', %q{Bermuda.} 29 | value 'BT', %q{Bhutan.} 30 | value 'BO', %q{Bolivia.} 31 | value 'BQ', %q{Bonaire, Sint Eustatius and Saba.} 32 | value 'BA', %q{Bosnia And Herzegovina.} 33 | value 'BW', %q{Botswana.} 34 | value 'BV', %q{Bouvet Island.} 35 | value 'BR', %q{Brazil.} 36 | value 'IO', %q{British Indian Ocean Territory.} 37 | value 'BN', %q{Brunei.} 38 | value 'BG', %q{Bulgaria.} 39 | value 'BF', %q{Burkina Faso.} 40 | value 'BI', %q{Burundi.} 41 | value 'KH', %q{Cambodia.} 42 | value 'CA', %q{Canada.} 43 | value 'CV', %q{Cape Verde.} 44 | value 'KY', %q{Cayman Islands.} 45 | value 'CF', %q{Central African Republic.} 46 | value 'TD', %q{Chad.} 47 | value 'CL', %q{Chile.} 48 | value 'CN', %q{China.} 49 | value 'CX', %q{Christmas Island.} 50 | value 'CC', %q{Cocos (Keeling) Islands.} 51 | value 'CO', %q{Colombia.} 52 | value 'KM', %q{Comoros.} 53 | value 'CG', %q{Congo.} 54 | value 'CD', %q{Congo, The Democratic Republic Of The.} 55 | value 'CK', %q{Cook Islands.} 56 | value 'CR', %q{Costa Rica.} 57 | value 'HR', %q{Croatia.} 58 | value 'CU', %q{Cuba.} 59 | value 'CW', %q{Curaçao.} 60 | value 'CY', %q{Cyprus.} 61 | value 'CZ', %q{Czech Republic.} 62 | value 'CI', %q{Côte d'Ivoire.} 63 | value 'DK', %q{Denmark.} 64 | value 'DJ', %q{Djibouti.} 65 | value 'DM', %q{Dominica.} 66 | value 'DO', %q{Dominican Republic.} 67 | value 'EC', %q{Ecuador.} 68 | value 'EG', %q{Egypt.} 69 | value 'SV', %q{El Salvador.} 70 | value 'GQ', %q{Equatorial Guinea.} 71 | value 'ER', %q{Eritrea.} 72 | value 'EE', %q{Estonia.} 73 | value 'ET', %q{Ethiopia.} 74 | value 'FK', %q{Falkland Islands (Malvinas).} 75 | value 'FO', %q{Faroe Islands.} 76 | value 'FJ', %q{Fiji.} 77 | value 'FI', %q{Finland.} 78 | value 'FR', %q{France.} 79 | value 'GF', %q{French Guiana.} 80 | value 'PF', %q{French Polynesia.} 81 | value 'TF', %q{French Southern Territories.} 82 | value 'GA', %q{Gabon.} 83 | value 'GM', %q{Gambia.} 84 | value 'GE', %q{Georgia.} 85 | value 'DE', %q{Germany.} 86 | value 'GH', %q{Ghana.} 87 | value 'GI', %q{Gibraltar.} 88 | value 'GR', %q{Greece.} 89 | value 'GL', %q{Greenland.} 90 | value 'GD', %q{Grenada.} 91 | value 'GP', %q{Guadeloupe.} 92 | value 'GT', %q{Guatemala.} 93 | value 'GG', %q{Guernsey.} 94 | value 'GN', %q{Guinea.} 95 | value 'GW', %q{Guinea Bissau.} 96 | value 'GY', %q{Guyana.} 97 | value 'HT', %q{Haiti.} 98 | value 'HM', %q{Heard Island And Mcdonald Islands.} 99 | value 'VA', %q{Holy See (Vatican City State).} 100 | value 'HN', %q{Honduras.} 101 | value 'HK', %q{Hong Kong.} 102 | value 'HU', %q{Hungary.} 103 | value 'IS', %q{Iceland.} 104 | value 'IN', %q{India.} 105 | value 'ID', %q{Indonesia.} 106 | value 'IR', %q{Iran, Islamic Republic Of.} 107 | value 'IQ', %q{Iraq.} 108 | value 'IE', %q{Ireland.} 109 | value 'IM', %q{Isle Of Man.} 110 | value 'IL', %q{Israel.} 111 | value 'IT', %q{Italy.} 112 | value 'JM', %q{Jamaica.} 113 | value 'JP', %q{Japan.} 114 | value 'JE', %q{Jersey.} 115 | value 'JO', %q{Jordan.} 116 | value 'KZ', %q{Kazakhstan.} 117 | value 'KE', %q{Kenya.} 118 | value 'KI', %q{Kiribati.} 119 | value 'KP', %q{Korea, Democratic People's Republic Of.} 120 | value 'XK', %q{Kosovo.} 121 | value 'KW', %q{Kuwait.} 122 | value 'KG', %q{Kyrgyzstan.} 123 | value 'LA', %q{Lao People's Democratic Republic.} 124 | value 'LV', %q{Latvia.} 125 | value 'LB', %q{Lebanon.} 126 | value 'LS', %q{Lesotho.} 127 | value 'LR', %q{Liberia.} 128 | value 'LY', %q{Libyan Arab Jamahiriya.} 129 | value 'LI', %q{Liechtenstein.} 130 | value 'LT', %q{Lithuania.} 131 | value 'LU', %q{Luxembourg.} 132 | value 'MO', %q{Macao.} 133 | value 'MK', %q{Macedonia, Republic Of.} 134 | value 'MG', %q{Madagascar.} 135 | value 'MW', %q{Malawi.} 136 | value 'MY', %q{Malaysia.} 137 | value 'MV', %q{Maldives.} 138 | value 'ML', %q{Mali.} 139 | value 'MT', %q{Malta.} 140 | value 'MQ', %q{Martinique.} 141 | value 'MR', %q{Mauritania.} 142 | value 'MU', %q{Mauritius.} 143 | value 'YT', %q{Mayotte.} 144 | value 'MX', %q{Mexico.} 145 | value 'MD', %q{Moldova, Republic of.} 146 | value 'MC', %q{Monaco.} 147 | value 'MN', %q{Mongolia.} 148 | value 'ME', %q{Montenegro.} 149 | value 'MS', %q{Montserrat.} 150 | value 'MA', %q{Morocco.} 151 | value 'MZ', %q{Mozambique.} 152 | value 'MM', %q{Myanmar.} 153 | value 'NA', %q{Namibia.} 154 | value 'NR', %q{Nauru.} 155 | value 'NP', %q{Nepal.} 156 | value 'NL', %q{Netherlands.} 157 | value 'AN', %q{Netherlands Antilles.} 158 | value 'NC', %q{New Caledonia.} 159 | value 'NZ', %q{New Zealand.} 160 | value 'NI', %q{Nicaragua.} 161 | value 'NE', %q{Niger.} 162 | value 'NG', %q{Nigeria.} 163 | value 'NU', %q{Niue.} 164 | value 'NF', %q{Norfolk Island.} 165 | value 'NO', %q{Norway.} 166 | value 'OM', %q{Oman.} 167 | value 'PK', %q{Pakistan.} 168 | value 'PS', %q{Palestinian Territory, Occupied.} 169 | value 'PA', %q{Panama.} 170 | value 'PG', %q{Papua New Guinea.} 171 | value 'PY', %q{Paraguay.} 172 | value 'PE', %q{Peru.} 173 | value 'PH', %q{Philippines.} 174 | value 'PN', %q{Pitcairn.} 175 | value 'PL', %q{Poland.} 176 | value 'PT', %q{Portugal.} 177 | value 'QA', %q{Qatar.} 178 | value 'CM', %q{Republic of Cameroon.} 179 | value 'RE', %q{Reunion.} 180 | value 'RO', %q{Romania.} 181 | value 'RU', %q{Russia.} 182 | value 'RW', %q{Rwanda.} 183 | value 'BL', %q{Saint Barthélemy.} 184 | value 'SH', %q{Saint Helena.} 185 | value 'KN', %q{Saint Kitts And Nevis.} 186 | value 'LC', %q{Saint Lucia.} 187 | value 'MF', %q{Saint Martin.} 188 | value 'PM', %q{Saint Pierre And Miquelon.} 189 | value 'WS', %q{Samoa.} 190 | value 'SM', %q{San Marino.} 191 | value 'ST', %q{Sao Tome And Principe.} 192 | value 'SA', %q{Saudi Arabia.} 193 | value 'SN', %q{Senegal.} 194 | value 'RS', %q{Serbia.} 195 | value 'SC', %q{Seychelles.} 196 | value 'SL', %q{Sierra Leone.} 197 | value 'SG', %q{Singapore.} 198 | value 'SX', %q{Sint Maarten.} 199 | value 'SK', %q{Slovakia.} 200 | value 'SI', %q{Slovenia.} 201 | value 'SB', %q{Solomon Islands.} 202 | value 'SO', %q{Somalia.} 203 | value 'ZA', %q{South Africa.} 204 | value 'GS', %q{South Georgia And The South Sandwich Islands.} 205 | value 'KR', %q{South Korea.} 206 | value 'SS', %q{South Sudan.} 207 | value 'ES', %q{Spain.} 208 | value 'LK', %q{Sri Lanka.} 209 | value 'VC', %q{St. Vincent.} 210 | value 'SD', %q{Sudan.} 211 | value 'SR', %q{Suriname.} 212 | value 'SJ', %q{Svalbard And Jan Mayen.} 213 | value 'SZ', %q{Swaziland.} 214 | value 'SE', %q{Sweden.} 215 | value 'CH', %q{Switzerland.} 216 | value 'SY', %q{Syria.} 217 | value 'TW', %q{Taiwan.} 218 | value 'TJ', %q{Tajikistan.} 219 | value 'TZ', %q{Tanzania, United Republic Of.} 220 | value 'TH', %q{Thailand.} 221 | value 'TL', %q{Timor Leste.} 222 | value 'TG', %q{Togo.} 223 | value 'TK', %q{Tokelau.} 224 | value 'TO', %q{Tonga.} 225 | value 'TT', %q{Trinidad and Tobago.} 226 | value 'TN', %q{Tunisia.} 227 | value 'TR', %q{Turkey.} 228 | value 'TM', %q{Turkmenistan.} 229 | value 'TC', %q{Turks and Caicos Islands.} 230 | value 'TV', %q{Tuvalu.} 231 | value 'UG', %q{Uganda.} 232 | value 'UA', %q{Ukraine.} 233 | value 'AE', %q{United Arab Emirates.} 234 | value 'GB', %q{United Kingdom.} 235 | value 'US', %q{United States.} 236 | value 'UM', %q{United States Minor Outlying Islands.} 237 | value 'UY', %q{Uruguay.} 238 | value 'UZ', %q{Uzbekistan.} 239 | value 'VU', %q{Vanuatu.} 240 | value 'VE', %q{Venezuela.} 241 | value 'VN', %q{Vietnam.} 242 | value 'VG', %q{Virgin Islands, British.} 243 | value 'WF', %q{Wallis And Futuna.} 244 | value 'EH', %q{Western Sahara.} 245 | value 'YE', %q{Yemen.} 246 | value 'ZM', %q{Zambia.} 247 | value 'ZW', %q{Zimbabwe.} 248 | end 249 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/credit_card.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::CreditCard < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'CreditCard' 5 | 6 | description %q{Credit card information used for a payment.} 7 | 8 | field :brand, ::GraphQL::Types::String, null: true 9 | def brand 10 | object.brand.upcase 11 | end 12 | 13 | field :expiry_month, ::GraphQL::Types::Int, null: true 14 | def expiry_month 15 | object.month 16 | end 17 | 18 | field :expiry_year, ::GraphQL::Types::Int, null: true 19 | def expiry_year 20 | object.year 21 | end 22 | 23 | field :first_digits, ::GraphQL::Types::String, null: true 24 | def first_digits 25 | raise ::Spree::GraphQL::NotImplementedError 26 | end 27 | 28 | field :first_name, ::GraphQL::Types::String, null: true 29 | def first_name 30 | object.first_name 31 | end 32 | 33 | field :last_digits, ::GraphQL::Types::String, null: true 34 | def last_digits 35 | object.last_digits 36 | end 37 | 38 | field :last_name, ::GraphQL::Types::String, null: true 39 | def last_name 40 | object.last_name 41 | end 42 | 43 | field :masked_number, ::GraphQL::Types::String, null: true do 44 | description %q{Masked credit card number with only the last 4 digits displayed} 45 | end 46 | def masked_number 47 | object.display_number 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/currency_code.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::CurrencyCode < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'CurrencyCode' 5 | description %q{Currency codes} 6 | value 'USD', %q{United States Dollars (USD).} 7 | value 'EUR', %q{Euro (EUR).} 8 | value 'GBP', %q{United Kingdom Pounds (GBP).} 9 | value 'CAD', %q{Canadian Dollars (CAD).} 10 | value 'AFN', %q{Afghan Afghani (AFN).} 11 | value 'ALL', %q{Albanian Lek (ALL).} 12 | value 'DZD', %q{Algerian Dinar (DZD).} 13 | value 'AOA', %q{Angolan Kwanza (AOA).} 14 | value 'ARS', %q{Argentine Pesos (ARS).} 15 | value 'AMD', %q{Armenian Dram (AMD).} 16 | value 'AWG', %q{Aruban Florin (AWG).} 17 | value 'AUD', %q{Australian Dollars (AUD).} 18 | value 'BBD', %q{Barbadian Dollar (BBD).} 19 | value 'AZN', %q{Azerbaijani Manat (AZN).} 20 | value 'BDT', %q{Bangladesh Taka (BDT).} 21 | value 'BSD', %q{Bahamian Dollar (BSD).} 22 | value 'BHD', %q{Bahraini Dinar (BHD).} 23 | value 'BIF', %q{Burundian Franc (BIF).} 24 | value 'BYR', %q{Belarusian Ruble (BYR).} 25 | value 'BZD', %q{Belize Dollar (BZD).} 26 | value 'BTN', %q{Bhutanese Ngultrum (BTN).} 27 | value 'BAM', %q{Bosnia and Herzegovina Convertible Mark (BAM).} 28 | value 'BRL', %q{Brazilian Real (BRL).} 29 | value 'BOB', %q{Bolivian Boliviano (BOB).} 30 | value 'BWP', %q{Botswana Pula (BWP).} 31 | value 'BND', %q{Brunei Dollar (BND).} 32 | value 'BGN', %q{Bulgarian Lev (BGN).} 33 | value 'MMK', %q{Burmese Kyat (MMK).} 34 | value 'KHR', %q{Cambodian Riel.} 35 | value 'CVE', %q{Cape Verdean escudo (CVE).} 36 | value 'KYD', %q{Cayman Dollars (KYD).} 37 | value 'XAF', %q{Central African CFA Franc (XAF).} 38 | value 'CLP', %q{Chilean Peso (CLP).} 39 | value 'CNY', %q{Chinese Yuan Renminbi (CNY).} 40 | value 'COP', %q{Colombian Peso (COP).} 41 | value 'KMF', %q{Comorian Franc (KMF).} 42 | value 'CDF', %q{Congolese franc (CDF).} 43 | value 'CRC', %q{Costa Rican Colones (CRC).} 44 | value 'HRK', %q{Croatian Kuna (HRK).} 45 | value 'CZK', %q{Czech Koruny (CZK).} 46 | value 'DKK', %q{Danish Kroner (DKK).} 47 | value 'DOP', %q{Dominican Peso (DOP).} 48 | value 'XCD', %q{East Caribbean Dollar (XCD).} 49 | value 'EGP', %q{Egyptian Pound (EGP).} 50 | value 'ETB', %q{Ethiopian Birr (ETB).} 51 | value 'XPF', %q{CFP Franc (XPF).} 52 | value 'FJD', %q{Fijian Dollars (FJD).} 53 | value 'GMD', %q{Gambian Dalasi (GMD).} 54 | value 'GHS', %q{Ghanaian Cedi (GHS).} 55 | value 'GTQ', %q{Guatemalan Quetzal (GTQ).} 56 | value 'GYD', %q{Guyanese Dollar (GYD).} 57 | value 'GEL', %q{Georgian Lari (GEL).} 58 | value 'HTG', %q{Haitian Gourde (HTG).} 59 | value 'HNL', %q{Honduran Lempira (HNL).} 60 | value 'HKD', %q{Hong Kong Dollars (HKD).} 61 | value 'HUF', %q{Hungarian Forint (HUF).} 62 | value 'ISK', %q{Icelandic Kronur (ISK).} 63 | value 'INR', %q{Indian Rupees (INR).} 64 | value 'IDR', %q{Indonesian Rupiah (IDR).} 65 | value 'ILS', %q{Israeli New Shekel (NIS).} 66 | value 'IQD', %q{Iraqi Dinar (IQD).} 67 | value 'JMD', %q{Jamaican Dollars (JMD).} 68 | value 'JPY', %q{Japanese Yen (JPY).} 69 | value 'JEP', %q{Jersey Pound.} 70 | value 'JOD', %q{Jordanian Dinar (JOD).} 71 | value 'KZT', %q{Kazakhstani Tenge (KZT).} 72 | value 'KES', %q{Kenyan Shilling (KES).} 73 | value 'KWD', %q{Kuwaiti Dinar (KWD).} 74 | value 'KGS', %q{Kyrgyzstani Som (KGS).} 75 | value 'LAK', %q{Laotian Kip (LAK).} 76 | value 'LVL', %q{Latvian Lati (LVL).} 77 | value 'LBP', %q{Lebanese Pounds (LBP).} 78 | value 'LSL', %q{Lesotho Loti (LSL).} 79 | value 'LRD', %q{Liberian Dollar (LRD).} 80 | value 'LTL', %q{Lithuanian Litai (LTL).} 81 | value 'MGA', %q{Malagasy Ariary (MGA).} 82 | value 'MKD', %q{Macedonia Denar (MKD).} 83 | value 'MOP', %q{Macanese Pataca (MOP).} 84 | value 'MWK', %q{Malawian Kwacha (MWK).} 85 | value 'MVR', %q{Maldivian Rufiyaa (MVR).} 86 | value 'MXN', %q{Mexican Pesos (MXN).} 87 | value 'MYR', %q{Malaysian Ringgits (MYR).} 88 | value 'MUR', %q{Mauritian Rupee (MUR).} 89 | value 'MDL', %q{Moldovan Leu (MDL).} 90 | value 'MAD', %q{Moroccan Dirham.} 91 | value 'MNT', %q{Mongolian Tugrik.} 92 | value 'MZN', %q{Mozambican Metical.} 93 | value 'NAD', %q{Namibian Dollar.} 94 | value 'NPR', %q{Nepalese Rupee (NPR).} 95 | value 'ANG', %q{Netherlands Antillean Guilder.} 96 | value 'NZD', %q{New Zealand Dollars (NZD).} 97 | value 'NIO', %q{Nicaraguan Córdoba (NIO).} 98 | value 'NGN', %q{Nigerian Naira (NGN).} 99 | value 'NOK', %q{Norwegian Kroner (NOK).} 100 | value 'OMR', %q{Omani Rial (OMR).} 101 | value 'PKR', %q{Pakistani Rupee (PKR).} 102 | value 'PGK', %q{Papua New Guinean Kina (PGK).} 103 | value 'PYG', %q{Paraguayan Guarani (PYG).} 104 | value 'PEN', %q{Peruvian Nuevo Sol (PEN).} 105 | value 'PHP', %q{Philippine Peso (PHP).} 106 | value 'PLN', %q{Polish Zlotych (PLN).} 107 | value 'QAR', %q{Qatari Rial (QAR).} 108 | value 'RON', %q{Romanian Lei (RON).} 109 | value 'RUB', %q{Russian Rubles (RUB).} 110 | value 'RWF', %q{Rwandan Franc (RWF).} 111 | value 'WST', %q{Samoan Tala (WST).} 112 | value 'SAR', %q{Saudi Riyal (SAR).} 113 | value 'STD', %q{Sao Tome And Principe Dobra (STD).} 114 | value 'RSD', %q{Serbian dinar (RSD).} 115 | value 'SCR', %q{Seychellois Rupee (SCR).} 116 | value 'SGD', %q{Singapore Dollars (SGD).} 117 | value 'SDG', %q{Sudanese Pound (SDG).} 118 | value 'SYP', %q{Syrian Pound (SYP).} 119 | value 'ZAR', %q{South African Rand (ZAR).} 120 | value 'KRW', %q{South Korean Won (KRW).} 121 | value 'SSP', %q{South Sudanese Pound (SSP).} 122 | value 'SBD', %q{Solomon Islands Dollar (SBD).} 123 | value 'LKR', %q{Sri Lankan Rupees (LKR).} 124 | value 'SRD', %q{Surinamese Dollar (SRD).} 125 | value 'SZL', %q{Swazi Lilangeni (SZL).} 126 | value 'SEK', %q{Swedish Kronor (SEK).} 127 | value 'CHF', %q{Swiss Francs (CHF).} 128 | value 'TWD', %q{Taiwan Dollars (TWD).} 129 | value 'THB', %q{Thai baht (THB).} 130 | value 'TZS', %q{Tanzanian Shilling (TZS).} 131 | value 'TTD', %q{Trinidad and Tobago Dollars (TTD).} 132 | value 'TND', %q{Tunisian Dinar (TND).} 133 | value 'TRY', %q{Turkish Lira (TRY).} 134 | value 'TMT', %q{Turkmenistani Manat (TMT).} 135 | value 'UGX', %q{Ugandan Shilling (UGX).} 136 | value 'UAH', %q{Ukrainian Hryvnia (UAH).} 137 | value 'AED', %q{United Arab Emirates Dirham (AED).} 138 | value 'UYU', %q{Uruguayan Pesos (UYU).} 139 | value 'UZS', %q{Uzbekistan som (UZS).} 140 | value 'VUV', %q{Vanuatu Vatu (VUV).} 141 | value 'VEF', %q{Venezuelan Bolivares (VEF).} 142 | value 'VND', %q{Vietnamese đồng (VND).} 143 | value 'XOF', %q{West African CFA franc (XOF).} 144 | value 'YER', %q{Yemeni Rial (YER).} 145 | value 'ZMW', %q{Zambian Kwacha (ZMW).} 146 | end 147 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/date_time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::DateTime < GraphQL::Types::ISO8601DateTime 4 | description %q{An ISO-8601 encoded UTC date time string.} 5 | end 6 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/decimal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Decimal < Spree::GraphQL::Schema::Types::BaseScalar 4 | graphql_name 'Decimal' 5 | description %q{A signed decimal number, which supports arbitrary precision and is serialized as a string.} 6 | end 7 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/domain.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Domain < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'Domain' 5 | 6 | description %q{Represents a web address.} 7 | 8 | field :host, ::GraphQL::Types::String, null: false do 9 | description %q{The host name of the domain (eg: `example.com`).} 10 | end 11 | def host 12 | object.host 13 | end 14 | 15 | field :ssl_enabled, ::GraphQL::Types::Boolean, null: false do 16 | description %q{Whether SSL is enabled or not.} 17 | end 18 | def ssl_enabled 19 | object.ssl_enabled 20 | end 21 | 22 | field :url, ::Spree::GraphQL::Schema::Types::URL, null: false do 23 | description %q{The URL of the domain (eg: `https://example.com`).} 24 | end 25 | def url 26 | object.url 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/html.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::HTML < Spree::GraphQL::Schema::Types::BaseScalar 4 | graphql_name 'HTML' 5 | description %q{A string containing HTML code.} 6 | end 7 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/image.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Image < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | graphql_name 'Image' 5 | 6 | def self.url_field_default_value 7 | default_style_value = Spree::Image.attachment_definitions.dig :attachment, :default_style 8 | 9 | if !default_style_value || Spree::GraphQL::Schema::Types::ImageStyle.values.none? do |_, enum_value| 10 | enum_value.value == default_style_value 11 | end 12 | return 13 | end 14 | 15 | default_style_value 16 | end 17 | 18 | field :alt_text, ::GraphQL::Types::String, null: true do 19 | description 'A word or phrase to share the nature or contents of an image.' 20 | end 21 | def alt_text 22 | object.alt 23 | end 24 | 25 | field :url, ::Spree::GraphQL::Schema::Types::URL, null: false do 26 | description 'The location of the image as a URL.' 27 | argument :style, 28 | Spree::GraphQL::Schema::Types::ImageStyle, 29 | required: false, 30 | default_value: owner.url_field_default_value, 31 | description: 'The desired image style.' 32 | end 33 | def url(style:) 34 | context[:helpers].asset_url object.url style 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/image_style.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::ImageStyle < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'ImageStyle' 5 | 6 | description 'The set of valid styles for the image. Image styles are alternative versions based on the original image 7 | which differ from the original image for post-processing methods, used for generating images with different sizes, 8 | cropping methods and other post-processing effects.' 9 | 10 | def self.image_styles 11 | Spree::Image.attachment_definitions[:attachment][:styles].keys 12 | end 13 | 14 | image_styles.sort.each do |style| 15 | value style.to_s.upcase, "#{style.inspect} style", value: style 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/mailing_address.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::MailingAddress < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | graphql_name 'MailingAddress' 5 | 6 | description %q{Represents a mailing address for customers and shipping.} 7 | 8 | field :address1, ::GraphQL::Types::String, null: true do 9 | description %q{The first line of the address. Typically the street address or PO Box number. 10 | } 11 | end 12 | def address1 13 | object.address1 14 | end 15 | 16 | field :address2, ::GraphQL::Types::String, null: true do 17 | description %q{The second line of the address. Typically the number of the apartment, suite, or unit. 18 | } 19 | end 20 | def address2 21 | object.address2 22 | end 23 | 24 | field :city, ::GraphQL::Types::String, null: true do 25 | description %q{The name of the city, district, village, or town. 26 | } 27 | end 28 | def city 29 | object.city 30 | end 31 | 32 | field :company, ::GraphQL::Types::String, null: true do 33 | description %q{The name of the customer's company or organization. 34 | } 35 | end 36 | def company 37 | object.company 38 | end 39 | 40 | field :country, ::GraphQL::Types::String, null: true do 41 | description %q{The name of the country. 42 | } 43 | end 44 | def country 45 | object.country.name 46 | end 47 | 48 | field :country_code_v2, ::Spree::GraphQL::Schema::Types::CountryCode, null: true do 49 | description %q{The two-letter code for the country of the address. 50 | 51 | For example, US. 52 | } 53 | end 54 | def country_code_v2 55 | object.country_iso 56 | end 57 | 58 | field :first_name, ::GraphQL::Types::String, null: true do 59 | description %q{The first name of the customer.} 60 | end 61 | def first_name 62 | object.firstname 63 | end 64 | 65 | field :formatted, [::GraphQL::Types::String], null: false do 66 | description %q{A formatted version of the address, customized by the provided arguments.} 67 | argument :with_name, ::GraphQL::Types::Boolean, required: false, default_value: false, description: %q{Whether to include the customer's name in the formatted address.} 68 | argument :with_company, ::GraphQL::Types::Boolean, required: false, default_value: true, description: %q{Whether to include the customer's company in the formatted address.} 69 | end 70 | def formatted(with_name:, with_company:) 71 | raise ::Spree::GraphQL::NotImplementedError 72 | end 73 | 74 | field :formatted_area, ::GraphQL::Types::String, null: true do 75 | description %q{A comma-separated list of the values for city, province, and country.} 76 | end 77 | def formatted_area 78 | [city, province, country].compact.join(', ') 79 | end 80 | 81 | field :last_name, ::GraphQL::Types::String, null: true do 82 | description %q{The last name of the customer.} 83 | end 84 | def last_name 85 | object.lastname 86 | end 87 | 88 | field :latitude, ::GraphQL::Types::Float, null: true do 89 | description %q{The latitude coordinate of the customer address.} 90 | end 91 | def latitude 92 | raise ::Spree::GraphQL::NotImplementedError 93 | end 94 | 95 | field :longitude, ::GraphQL::Types::Float, null: true do 96 | description %q{The longitude coordinate of the customer address.} 97 | end 98 | def longitude 99 | raise ::Spree::GraphQL::NotImplementedError 100 | end 101 | 102 | field :name, ::GraphQL::Types::String, null: true do 103 | description %q{The full name of the customer, based on firstName and lastName. 104 | } 105 | end 106 | def name 107 | object.full_name 108 | end 109 | 110 | field :phone, ::GraphQL::Types::String, null: true do 111 | description %q{A unique phone number for the customer. 112 | 113 | Formatted using E.164 standard. For example, _+16135551111_. 114 | } 115 | end 116 | def phone 117 | object.phone 118 | end 119 | 120 | field :province, ::GraphQL::Types::String, null: true do 121 | description %q{The region of the address, such as the province, state, or district.} 122 | end 123 | def province 124 | object.state.try(:name) || object.state_name 125 | end 126 | 127 | field :province_code, ::GraphQL::Types::String, null: true do 128 | description %q{The two-letter code for the region. 129 | 130 | For example, ON. 131 | } 132 | end 133 | def province_code 134 | object.state.try(:abbr) 135 | end 136 | 137 | field :zip, ::GraphQL::Types::String, null: true do 138 | description %q{The zip or postal code of the address.} 139 | end 140 | def zip 141 | object.zipcode 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/money.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Money < Spree::GraphQL::Schema::Types::BaseObject 4 | description <<~MD 5 | A monetary value with currency. 6 | 7 | To format currencies, combine this type’s amount and currencyCode fields with your client’s locale. 8 | 9 | For example, in JavaScript you could use Intl.NumberFormat: 10 | 11 | ```js 12 | new Intl.NumberFormat(locale, { 13 | style: 'currency', 14 | currency: currencyCode 15 | }).format(amount); 16 | ``` 17 | 18 | Other formatting libraries include: 19 | 20 | * iOS - [NumberFormatter](https://developer.apple.com/documentation/foundation/numberformatter) 21 | * Android - [NumberFormat](https://developer.android.com/reference/java/text/NumberFormat.html) 22 | * PHP - [NumberFormatter](http://php.net/manual/en/class.numberformatter.php) 23 | 24 | For a more general solution, the [Unicode CLDR number formatting database] is available with many implementations 25 | (such as [TwitterCldr](https://github.com/twitter/twitter-cldr-rb)). 26 | MD 27 | 28 | field :amount, Spree::GraphQL::Schema::Types::Decimal, null: false do 29 | description 'Decimal money amount.' 30 | end 31 | def amount 32 | object.to_d 33 | end 34 | 35 | field :currency_code, Spree::GraphQL::Schema::Types::CurrencyCode, null: false do 36 | description 'Currency of the money.' 37 | end 38 | def currency_code 39 | object.currency 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/mutation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Mutation < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'Mutation' 5 | description %q{The schema’s entry-point for mutations. This acts as the public, top-level API from which all mutation queries must start.} 6 | end 7 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/option_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::OptionType < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | graphql_name 'OptionType' 5 | 6 | description <<~MD 7 | Option types denote the different options for a variant. A typical option type would be a size, with that option 8 | type’s values being something such as "Small", "Medium" and "Large". Another typical option type could be a color, 9 | such as "Red", "Green", or "Blue". 10 | A product can be assigned many option types, but must be assigned at least one if you wish to create variants for 11 | that product. 12 | MD 13 | 14 | field :name, ::GraphQL::Types::String, null: false do 15 | description 'The option type’s name.' 16 | end 17 | def name 18 | object.name 19 | end 20 | 21 | field :presentation, ::GraphQL::Types::String, null: false do 22 | description 'The option type’s presentation.' 23 | end 24 | def presentation 25 | object.presentation 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/option_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::OptionValue < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | include Spree::GraphQL::LazyResolver::TypeHelper 5 | 6 | graphql_name 'OptionValue' 7 | 8 | description <<~MD 9 | An OptionValue represents every single value an OptionType can assume. For instance, if there is a "Size" 10 | OptionType, then the OptionValues for it could be like "Small", "Medium", and "Large". 11 | MD 12 | 13 | field :name, ::GraphQL::Types::String, null: false do 14 | description 'The option value’s name.' 15 | end 16 | def name 17 | object.name 18 | end 19 | 20 | field :option_type, ::Spree::GraphQL::Schema::Types::OptionType, null: false do 21 | description 'The option value’s option type.' 22 | end 23 | delegate :option_type, to: :lazy_resolver 24 | 25 | field :presentation, ::GraphQL::Types::String, null: false do 26 | description 'The option value’s presentation.' 27 | end 28 | def presentation 29 | object.presentation 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/payment_settings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::PaymentSettings < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'PaymentSettings' 5 | 6 | description %q{Settings related to payments.} 7 | 8 | field :country_code, ::Spree::GraphQL::Schema::Types::CountryCode, null: false do 9 | description %q{The country where the shop is located.} 10 | end 11 | def country_code 12 | object.country_code 13 | end 14 | 15 | field :currency_code, ::Spree::GraphQL::Schema::Types::CurrencyCode, null: false do 16 | description %q{The three-letter code for the currency that the shop accepts.} 17 | end 18 | def currency_code 19 | object.currency_code 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/product.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Product < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | include Spree::GraphQL::LazyResolver::TypeHelper 5 | 6 | graphql_name 'Product' 7 | 8 | description 'A product represents an individual item for sale in a Solidus store. Products are often physical, but 9 | they don’t have to be. For example, a digital download (such as a movie, music or ebook file) also qualifies as a 10 | product, as do services (such as equipment rental, work for hire, customization of another product or an extended 11 | warranty).' 12 | 13 | field :available_for_sale, ::GraphQL::Types::Boolean, null: false do 14 | description 'Indicates if at least one product variant is available for sale.' 15 | end 16 | def available_for_sale 17 | raise ::Spree::GraphQL::NotImplementedError 18 | end 19 | 20 | field :available_on, ::Spree::GraphQL::Schema::Types::DateTime, null: false do 21 | description 'The first date and the time the product becomes available for sale online in your shop. If the 22 | `available_on` attribute is not set, the product does not appear among the store’s products for sale.' 23 | end 24 | def published_at 25 | object.available_on 26 | end 27 | 28 | field :collections, ::Spree::GraphQL::Schema::Types::Collection.connection_type, null: false do 29 | description 'List of collections a product belongs to.' 30 | argument :reverse, 31 | ::GraphQL::Types::Boolean, 32 | required: false, 33 | default_value: false, 34 | description: 'Reverse the order of the underlying list.' 35 | end 36 | def collections(*) 37 | raise ::Spree::GraphQL::NotImplementedError 38 | end 39 | 40 | field :created_at, ::Spree::GraphQL::Schema::Types::DateTime, null: false do 41 | description 'The date and time when the product was created.' 42 | end 43 | def created_at 44 | object.created_at 45 | end 46 | 47 | field :description, ::GraphQL::Types::String, null: true do 48 | description 'Description of the product.' 49 | end 50 | def description 51 | object.description 52 | end 53 | 54 | field :images, ::Spree::GraphQL::Schema::Types::Image.connection_type, null: false do 55 | description 'The product’s images.' 56 | argument :query, 57 | [Spree::GraphQL::Schema::Inputs::RansackQuery], 58 | required: false, 59 | default_value: [{ 'key' => 's', 'value' => 'position asc' }], 60 | description: 'List of Ransack queries, can be used to filter and sort the results.' 61 | end 62 | def images(query:) 63 | query = Spree::GraphQL::Schema::Inputs::RansackQuery.queries_to_ransack_query(query) 64 | object.images.ransack(query).result 65 | end 66 | 67 | field :master_variant, ::Spree::GraphQL::Schema::Types::Variant, null: false do 68 | description 'The product’s master variant.' 69 | end 70 | def master_variant 71 | object.master 72 | end 73 | delegate :master_variant, to: :lazy_resolver 74 | 75 | field :name, ::GraphQL::Types::String, null: false do 76 | description 'The product’s name.' 77 | end 78 | def name 79 | object.name 80 | end 81 | 82 | field :price, ::Spree::GraphQL::Schema::Types::Money, null: false do 83 | description 'The date and time when the product was last modified.' 84 | end 85 | def price 86 | object.price_for(context[:helpers].current_pricing_options) 87 | end 88 | 89 | field :slug, ::GraphQL::Types::String, null: false do 90 | description 'A human-friendly unique string for the Product automatically generated from its title.' 91 | end 92 | def slug 93 | object.slug 94 | end 95 | 96 | field :updated_at, ::Spree::GraphQL::Schema::Types::DateTime, null: false do 97 | description 'The date and time when the product was last modified.' 98 | end 99 | def updated_at 100 | object.updated_at 101 | end 102 | 103 | field :variants, ::Spree::GraphQL::Schema::Types::Variant.connection_type, null: false do 104 | description 'List of the product’s variants.' 105 | argument :including_master, 106 | ::GraphQL::Types::Boolean, 107 | required: false, 108 | default_value: false, 109 | description: 'Whether the returned variants should include the master variant or not.' 110 | argument :query, 111 | [Spree::GraphQL::Schema::Inputs::RansackQuery], 112 | required: false, 113 | default_value: [{ 'key' => 's', 'value' => 'position asc' }], 114 | description: 'List of Ransack queries, can be used to filter and sort the results.' 115 | end 116 | delegate :variants, to: :lazy_resolver 117 | end 118 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/product_collection_sort_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::ProductCollectionSortKeys < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'ProductCollectionSortKeys' 5 | 6 | description %q{The set of valid sort keys for the products query.} 7 | 8 | value 'TITLE', %q{Sort by the `title` value.} 9 | value 'PRICE', %q{Sort by the `price` value.} 10 | value 'BEST_SELLING', %q{Sort by the `best-selling` value.} 11 | value 'CREATED', %q{Sort by the `created` value.} 12 | value 'ID', %q{Sort by the `id` value.} 13 | value 'MANUAL', %q{Sort by the `manual` value.} 14 | value 'COLLECTION_DEFAULT', %q{Sort by the `collection-default` value.} 15 | value 'RELEVANCE', %q{During a search (i.e. when the `query` parameter has been specified on the connection) this sorts the 16 | results by relevance to the search term(s). When no search query is specified, this sort key is not 17 | deterministic and should not be used. 18 | } 19 | 20 | def self.apply!(r, **args) 21 | if args[:sort_key] 22 | r.reorder!( 23 | case args[:sort_key] 24 | when 'TITLE' 25 | :name 26 | when 'PRICE' 27 | raise ::Spree::GraphQL::NotImplementedError 28 | when 'BEST_SELLING' 29 | raise ::Spree::GraphQL::NotImplementedError 30 | when 'CREATED' 31 | :created_at 32 | when 'ID' 33 | :id 34 | when 'MANUAL' 35 | raise ::Spree::GraphQL::NotImplementedError 36 | when 'COLLECTION_DEFAULT' 37 | # TODO Should be set to collection's chosen value 38 | # if/when this field gets added to the model. 39 | when 'RELEVANCE' 40 | raise ::Spree::GraphQL::NotImplementedError 41 | else 42 | raise ::Spree::GraphQL::NotImplementedError 43 | end 44 | ) 45 | end 46 | r.reverse_order! if args[:reverse] 47 | r 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/product_sort_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::ProductSortKeys < Spree::GraphQL::Schema::Types::BaseEnum 4 | graphql_name 'ProductSortKeys' 5 | 6 | description %q{The set of valid sort keys for the products query.} 7 | 8 | value 'NAME', %q{Sort by the `name` value.} 9 | value 'PRODUCT_TYPE', %q{Sort by the `product_type` value.} 10 | value 'VENDOR', %q{Sort by the `vendor` value.} 11 | value 'UPDATED_AT', %q{Sort by the `updated_at` value.} 12 | value 'CREATED_AT', %q{Sort by the `created_at` value.} 13 | value 'BEST_SELLING', %q{Sort by the `best_selling` value.} 14 | value 'PRICE', %q{Sort by the `price` value.} 15 | value 'ID', %q{Sort by the `id` value.} 16 | value 'RELEVANCE', %q{During a search (i.e. when the `query` parameter has been specified on the connection) this sorts the 17 | results by relevance to the search term(s). When no search query is specified, this sort key is not 18 | deterministic and should not be used. 19 | } 20 | 21 | def self.apply!(r, **args) 22 | if args[:sort_key] 23 | r.reorder!( 24 | case args[:sort_key] 25 | when 'NAME' 26 | :name 27 | when 'PRODUCT_TYPE' 28 | raise ::Spree::GraphQL::NotImplementedError 29 | when 'VENDOR' 30 | raise ::Spree::GraphQL::NotImplementedError 31 | when 'UPDATED_AT' 32 | :updated_at 33 | when 'CREATED_AT' 34 | :created_at 35 | when 'BEST_SELLING' 36 | raise ::Spree::GraphQL::NotImplementedError 37 | when 'PRICE' 38 | raise ::Spree::GraphQL::NotImplementedError 39 | when 'ID' 40 | :id 41 | when 'RELEVANCE' 42 | raise ::Spree::GraphQL::NotImplementedError 43 | else 44 | raise ::Spree::GraphQL::NotImplementedError 45 | end 46 | ) 47 | end 48 | r.reverse_order! if args[:reverse] 49 | r 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/query_root.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::QueryRoot < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'QueryRoot' 5 | 6 | description 'The schema’s entry-point for queries. This acts as the public, top-level API from which all queries must 7 | start.' 8 | 9 | field :node, field: ::GraphQL::Relay::Node.field 10 | field :nodes, field: ::GraphQL::Relay::Node.plural_field 11 | 12 | field :product_by_slug, ::Spree::GraphQL::Schema::Types::Product, null: true do 13 | description 'Find a product by its slug.' 14 | argument :slug, ::GraphQL::Types::String, required: true, description: 'The handle of the product.' 15 | end 16 | def product_by_slug(slug:) 17 | Spree::Product.find_by_slug(slug) 18 | end 19 | 20 | field :products, ::Spree::GraphQL::Schema::Types::Product.connection_type, null: false do 21 | description 'List of the products.' 22 | # GraphQL arguments can’t be hashes with open keys, so we have to define `:query` argument as an array of 23 | # key-value tuples. 24 | argument :query, 25 | [Spree::GraphQL::Schema::Inputs::RansackQuery], 26 | required: false, 27 | default_value: [{ 'key' => 's', 'value' => 'id asc' }], 28 | description: 'List of Ransack queries, can be used to filter and sort the results.' 29 | end 30 | def products(query:) 31 | query = Spree::GraphQL::Schema::Inputs::RansackQuery.queries_to_ransack_query(query) 32 | Spree::Product.ransack(query).result 33 | end 34 | 35 | field :shop, ::Spree::GraphQL::Schema::Types::Shop, null: false do 36 | description nil 37 | end 38 | def shop 39 | context[:current_store] 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/shop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Shop < Spree::GraphQL::Schema::Types::BaseObject 4 | graphql_name 'Shop' 5 | 6 | description %q{Shop represents a collection of the general settings and information about the shop.} 7 | 8 | field :collection_by_handle, ::Spree::GraphQL::Schema::Types::Collection, null: true do 9 | description %q{Find a collection by its handle.} 10 | argument :handle, ::GraphQL::Types::String, required: true, description: %q{The handle of the collection.} 11 | end 12 | def collection_by_handle(handle:) 13 | ::Spree::Taxon.find_by(permalink: handle) 14 | end 15 | 16 | field :collections, ::Spree::GraphQL::Schema::Types::Collection.connection_type, null: false do 17 | description %q{List of the shop’s collections.} 18 | argument :reverse, ::GraphQL::Types::Boolean, required: false, default_value: false, description: %q{Reverse the order of the underlying list.} 19 | argument :sort_key, ::Spree::GraphQL::Schema::Types::CollectionSortKeys, required: false, default_value: 'ID', description: %q{Sort the underlying list by the given key.} 20 | argument :query, ::GraphQL::Types::String, required: false, default_value: nil, description: %q{Supported filter parameters: 21 | - `title` 22 | - `collection_type` 23 | - `updated_at` 24 | 25 | See the detailed [search syntax](https://help.solidus.io/api/getting-started/search-syntax). 26 | } 27 | end 28 | def collections(reverse:, sort_key:, query:) 29 | raise ::Spree::GraphQL::NotImplementedError if query 30 | 31 | ::Spree::GraphQL::Schema::Types::CollectionSortKeys.apply!( 32 | ::Spree::Taxonomy.all, 33 | reverse: reverse, 34 | sort_key: sort_key 35 | ) 36 | end 37 | 38 | field :description, ::GraphQL::Types::String, null: true do 39 | description %q{A description of the shop.} 40 | end 41 | def description 42 | object.meta_description || '' 43 | end 44 | 45 | field :money_format, ::GraphQL::Types::String, null: false do 46 | description %q{A string representing the way currency is formatted when the currency isn’t specified.} 47 | end 48 | def money_format 49 | format = ::Spree::Money.new(123_456_789, currency: Spree::Config.currency, no_cents: true).format 50 | format.sub!(/1.+?9/, '{{amount}}') 51 | format 52 | end 53 | 54 | field :name, ::GraphQL::Types::String, null: false do 55 | description %q{The shop’s name.} 56 | end 57 | def name 58 | object.name 59 | end 60 | 61 | field :payment_settings, ::Spree::GraphQL::Schema::Types::PaymentSettings, null: false do 62 | description %q{Settings related to payments.} 63 | end 64 | def payment_settings 65 | OpenStruct.new( 66 | country_code: object.cart_tax_country_iso, 67 | currency_code: object.default_currency 68 | # acceptedCardBrands: [], 69 | # cardVaultUrl: '', 70 | # solidusPaymentsAccountId: '', 71 | # supportedDigitalWallets: '', 72 | ) 73 | end 74 | 75 | field :primary_domain, ::Spree::GraphQL::Schema::Types::Domain, null: false do 76 | description %q{The shop’s primary domain.} 77 | end 78 | def primary_domain 79 | ::Spree::GraphQL::Domain.new(context[:current_store].url, Rails.configuration.force_ssl) 80 | end 81 | 82 | field :product_by_handle, ::Spree::GraphQL::Schema::Types::Product, null: true do 83 | description %q{Find a product by its handle.} 84 | argument :handle, ::GraphQL::Types::String, required: true, description: %q{The handle of the product.} 85 | end 86 | def product_by_handle(handle:) 87 | ::Spree::Product.find_by(slug: handle) 88 | end 89 | 90 | field :products, ::Spree::GraphQL::Schema::Types::Product.connection_type, null: false do 91 | description %q{List of the shop’s products.} 92 | argument :reverse, ::GraphQL::Types::Boolean, required: false, default_value: false, description: %q{Reverse the order of the underlying list.} 93 | argument :sort_key, ::Spree::GraphQL::Schema::Types::ProductSortKeys, required: false, default_value: 'ID', description: %q{Sort the underlying list by the given key.} 94 | argument :query, ::GraphQL::Types::String, required: false, default_value: nil, description: %q{Supported filter parameters: 95 | - `title` 96 | - `product_type` 97 | - `vendor` 98 | - `created_at` 99 | - `updated_at` 100 | - `variants.price` 101 | - `tag` 102 | 103 | See the detailed [search syntax](https://help.solidus.io/api/getting-started/search-syntax). 104 | } 105 | end 106 | def products(reverse:, sort_key:, query:) 107 | raise ::Spree::GraphQL::NotImplementedError if query 108 | 109 | ::Spree::GraphQL::Schema::Types::ProductSortKeys.apply!( 110 | ::Spree::Product.all, 111 | reverse: reverse, 112 | sort_key: sort_key 113 | ) 114 | end 115 | 116 | field :ships_to_countries, [::Spree::GraphQL::Schema::Types::CountryCode], null: false do 117 | description %q{Countries that the shop ships to.} 118 | end 119 | def ships_to_countries 120 | context[:helpers].available_countries.map(&:iso) 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/url.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::URL < Spree::GraphQL::Schema::Types::BaseScalar 4 | graphql_name 'URL' 5 | description %q{An RFC 3986 and RFC 3987 compliant URI string.} 6 | end 7 | -------------------------------------------------------------------------------- /lib/spree/graphql/schema/types/variant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Spree::GraphQL::Schema::Types::Variant < Spree::GraphQL::Schema::Types::BaseObjectNode 4 | include Spree::GraphQL::LazyResolver::TypeHelper 5 | 6 | graphql_name 'Variant' 7 | 8 | description <<~MD 9 | A product variant represents a different version of a product, such as differing sizes or differing colors. 10 | MD 11 | 12 | field :available_for_sale, ::GraphQL::Types::Boolean, null: false do 13 | description 'Indicates if the product variant is available for sale.' 14 | end 15 | def available_for_sale 16 | raise ::Spree::GraphQL::NotImplementedError 17 | end 18 | 19 | field :images, ::Spree::GraphQL::Schema::Types::Image.connection_type, null: false do 20 | description 'The product variant’s images.' 21 | argument :query, 22 | [Spree::GraphQL::Schema::Inputs::RansackQuery], 23 | required: false, 24 | default_value: [{ 'key' => 's', 'value' => 'position asc' }], 25 | description: 'List of Ransack queries, can be used to filter and sort the results.' 26 | end 27 | delegate :images, to: :lazy_resolver 28 | 29 | field :is_master, ::GraphQL::Types::Boolean, null: false, resolver_method: :master? do 30 | description 'Indicates if the product variant is the master variant.' 31 | end 32 | def master? 33 | object.is_master? 34 | end 35 | 36 | field :option_values, ::Spree::GraphQL::Schema::Types::OptionValue.connection_type, null: false do 37 | description 'The product variant’s option values.' 38 | end 39 | delegate :option_values, to: :lazy_resolver 40 | 41 | field :price, ::Spree::GraphQL::Schema::Types::Money, null: false do 42 | description 'The product variant’s price.' 43 | end 44 | def price 45 | Spree::GraphQL::LazyResolvers.for(self.class).new(object, context).price 46 | end 47 | 48 | field :prices, ::Spree::GraphQL::Schema::Types::Money.connection_type, null: false do 49 | description 'The product variant’s prices.' 50 | end 51 | delegate :prices, to: :lazy_resolver 52 | 53 | field :product, ::Spree::GraphQL::Schema::Types::Product, null: false do 54 | description 'The product object that the product variant belongs to.' 55 | end 56 | delegate :product, to: :lazy_resolver 57 | 58 | field :sku, ::GraphQL::Types::String, null: true do 59 | description 'The SKU (stock keeping unit) associated with the variant.' 60 | end 61 | def sku 62 | object.sku 63 | end 64 | 65 | field :title, ::GraphQL::Types::String, null: false do 66 | description 'The product variant’s title.' 67 | end 68 | def title 69 | object.name 70 | end 71 | 72 | field :weight, ::GraphQL::Types::Float, null: true do 73 | description 'The weight of the product variant in the unit system specified with `weight_unit`.' 74 | end 75 | def weight 76 | object.weight 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /solidus_graphql_api.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $:.push File.expand_path('lib', __dir__) 4 | require 'solidus_graphql_api/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.author = 'Daniel Honig' 8 | s.name = 'solidus_graphql_api' 9 | s.version = SolidusGraphqlApi::VERSION 10 | s.summary = 'Solidus GraphQL API' 11 | s.description = 'GraphQL comes to Solidus' 12 | s.license = 'BSD-3-Clause' 13 | s.files = Dir['{app,config,db,lib}/**/*', 'LICENSE', 'Rakefile', 'README.md'] 14 | s.bindir = 'exe' 15 | s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } 16 | 17 | s.add_dependency 'graphql', '~> 1.9.3' 18 | s.add_dependency 'solidus_core', '>= 2.7.0' 19 | 20 | s.add_development_dependency 'database_cleaner' 21 | # s.add_development_dependency 'byebug' 22 | s.add_development_dependency 'factory_bot' 23 | s.add_development_dependency 'ffaker' 24 | s.add_development_dependency 'rspec' 25 | s.add_development_dependency 'rspec-rails' 26 | s.add_development_dependency 'rubocop', '~> 0.66.0' 27 | s.add_development_dependency 'rubocop-rspec', '~> 1.32.0' 28 | s.add_development_dependency 'solidus_support', '>= 0.1.3' 29 | s.add_development_dependency 'sqlite3', '~> 1.3.6' 30 | end 31 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['RAILS_ENV'] ||= 'test' 4 | 5 | require File.expand_path('dummy/config/environment', __dir__) 6 | abort('The Rails environment is running in production mode!') if Rails.env.production? 7 | 8 | require 'rspec/rails' 9 | require 'factory_bot' 10 | require 'spree/testing_support/factories' 11 | require 'spree/testing_support/preferences' 12 | require 'solidus_support/extension/rails_helper' 13 | 14 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 15 | Dir[File.join(__dir__, 'support/**/*.rb')].each { |f| require f } 16 | 17 | RSpec.configure do |config| 18 | config.backtrace_exclusion_patterns = [%r{gems/activesupport}, %r{gems/actionpack}, %r{gems/rspec}] 19 | config.infer_spec_type_from_file_location! 20 | 21 | # Allows RSpec to persist some state between runs in order to support 22 | # the `--only-failures` and `--next-failure` CLI options. We recommend 23 | # you configure your source control system to ignore this file. 24 | config.example_status_persistence_file_path = 'spec/examples.txt' 25 | 26 | config.define_derived_metadata(file_path: %r{/spec/spree/graphql/schema/}) do |metadata| 27 | metadata[:type] = :graphql 28 | end 29 | 30 | config.shared_context_metadata_behavior = :apply_to_host_groups 31 | 32 | config.include Spree::GraphQL::Spec::Helpers, type: :graphql 33 | 34 | config.before do 35 | Spree::Core::Engine.routes.draw do 36 | post :graphql, to: 'graphql#execute' 37 | end 38 | end 39 | 40 | config.before type: :graphql do 41 | Spree::GraphQL::LazyResolver.clear_cache 42 | end 43 | 44 | config.after(:all) do 45 | Rails.application.reload_routes! 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/spree/graphql/domain_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree 6 | describe 'Domain' do 7 | let!(:host1) { 'example.com' } 8 | let!(:host2) { 'test.org' } 9 | let!(:ssl1) { false } 10 | let!(:ssl2) { true } 11 | let!(:url1) { 'http://example.com' } 12 | let!(:url2) { 'https://test.org' } 13 | let!(:domain1) { ::Spree::GraphQL::Domain.new(host1, ssl1) } 14 | let!(:domain2) { ::Spree::GraphQL::Domain.new(host2, ssl2) } 15 | 16 | # host: The host name of the domain (eg: `example.com`). 17 | # @return [Types::String!] 18 | describe 'host' do 19 | it 'succeeds' do 20 | expect(domain1.host).to eq(host1) 21 | expect(domain2.host).to eq(host2) 22 | end 23 | end 24 | 25 | # sslEnabled: Whether SSL is enabled or not. 26 | # @return [Types::Boolean!] 27 | describe 'sslEnabled' do 28 | it 'succeeds' do 29 | expect(domain1.ssl_enabled).to eq(ssl1) 30 | expect(domain2.ssl_enabled).to eq(ssl2) 31 | end 32 | end 33 | 34 | # url: The URL of the domain (eg: `https://example.com`). 35 | # @return [Types::URL!] 36 | describe 'url' do 37 | it 'succeeds' do 38 | expect(domain1.url).to eq(url1) 39 | expect(domain2.url).to eq(url2) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/spree/graphql/lazy_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::LazyResolver do 6 | describe '.adapter' do 7 | include_context 'when the lazy resolver adapter is BatchLoader' do 8 | subject(:returned_value) { described_class.adapter } 9 | 10 | it { is_expected.to eq Spree::GraphQL::LazyResolver::Adapters::BatchLoaderAdapter } 11 | end 12 | end 13 | 14 | described_class::METHODS_DELEGATED_TO_ADAPTER.each do |delegated_method| 15 | describe ".#{delegated_method}" do 16 | include_context 'when the lazy resolver adapter is BatchLoader' do 17 | it 'gets delegated to the adapter' do 18 | expect(described_class.adapter).to receive(delegated_method) 19 | described_class.send delegated_method 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/spree/graphql/lazy_resolvers_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::LazyResolvers do 6 | describe '.for' do 7 | subject(:returned_value) { described_class.for graphql_type_class } 8 | 9 | include_context 'when the lazy resolver adapter is BatchLoader' do 10 | context 'when it gets called with a GraphQL type class having a related lazy resolver' do 11 | let(:graphql_type_class) { Spree::GraphQL::Schema::Types::Product } 12 | 13 | it 'returns the related lazy resolver' do 14 | expect(returned_value).to eq Spree::GraphQL::LazyResolvers::BatchLoader::Product 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/collection_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::Schema::Types::Collection do 6 | let(:collection_description) { ' Not stripped collection description ' } 7 | let!(:collection) do 8 | create :taxon, 9 | description: collection_description, 10 | permalink: 'handle', 11 | name: 'Taxon Title', 12 | products: [product, product2] 13 | end 14 | let(:product) do 15 | create :product, 16 | name: 'B Product', 17 | description: 'Product description', 18 | slug: 'product1' 19 | end 20 | let(:product2) do 21 | create :product, 22 | name: 'A Product', 23 | description: nil, 24 | slug: 'product2' 25 | end 26 | let(:products) { collection.products } 27 | let(:ctx) { { current_store: current_store } } 28 | let(:variables) {} 29 | 30 | before { create(:store) } 31 | 32 | describe 'fields' do 33 | let(:query) do 34 | %q{ 35 | query { 36 | shop { 37 | collectionByHandle(handle: "handle") { 38 | description 39 | handle 40 | id 41 | title 42 | updatedAt 43 | } 44 | } 45 | } 46 | } 47 | end 48 | let(:result) do 49 | { 50 | data: { 51 | shop: { 52 | collectionByHandle: { 53 | description: collection_description.strip, 54 | handle: collection.permalink, 55 | id: ::Spree::GraphQL::Schema.id_from_object(collection), 56 | title: collection.name, 57 | updatedAt: collection.updated_at.iso8601 58 | } 59 | } 60 | } 61 | } 62 | end 63 | 64 | it 'succeeds' do 65 | execute 66 | expect(response_hash).to eq(result_hash) 67 | end 68 | end 69 | 70 | describe 'products' do 71 | let(:query) do 72 | %q{ 73 | query { 74 | shop { 75 | collectionByHandle(handle: "handle") { 76 | products(first: 2) { 77 | nodes { 78 | id 79 | name 80 | slug 81 | } 82 | } 83 | reverse: products(first: 1, reverse: true) { 84 | nodes { 85 | id 86 | name 87 | slug 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | end 95 | let(:result) do 96 | { 97 | data: { 98 | shop: { 99 | collectionByHandle: { 100 | products: { 101 | nodes: [ 102 | { 103 | id: ::Spree::GraphQL::Schema.id_from_object(products.first), 104 | name: products.first.name, 105 | slug: products.first.slug 106 | }, 107 | { 108 | id: ::Spree::GraphQL::Schema.id_from_object(products.second), 109 | name: products.second.name, 110 | slug: products.second.slug 111 | } 112 | ] 113 | }, 114 | reverse: { 115 | nodes: [ 116 | { 117 | id: ::Spree::GraphQL::Schema.id_from_object(products.last), 118 | name: products.last.name, 119 | slug: products.last.slug 120 | } 121 | ] 122 | } 123 | } 124 | } 125 | } 126 | } 127 | end 128 | 129 | it 'succeeds' do 130 | execute 131 | expect(response_hash).to eq(result_hash) 132 | end 133 | 134 | describe 'sortKey' do 135 | let(:query) do 136 | %q{ 137 | query { 138 | shop { 139 | collectionByHandle(handle: "handle") { 140 | products(first: 1, sortKey: TITLE, reverse: false) { 141 | nodes { 142 | id 143 | name 144 | slug 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | end 152 | let(:result) do 153 | { 154 | data: { 155 | shop: { 156 | collectionByHandle: { 157 | products: { 158 | nodes: [ 159 | { 160 | id: ::Spree::GraphQL::Schema.id_from_object(products.last), 161 | name: products.last.name, 162 | slug: products.last.slug 163 | } 164 | ] 165 | } 166 | } 167 | } 168 | } 169 | } 170 | end 171 | 172 | it 'succeeds' do 173 | execute 174 | expect(response_hash).to eq(result_hash) 175 | end 176 | end 177 | end 178 | end 179 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/country_code_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::CountryCode' do 7 | let!(:country_code) { create(:country_code) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/credit_card_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | class Spree::GraphQL::Schema::Types::QueryRoot < Spree::GraphQL::Schema::Types::BaseObject 6 | field :test_credit_card, ::Spree::GraphQL::Schema::Types::CreditCard, null: false 7 | 8 | def test_credit_card 9 | context[:credit_card] 10 | end 11 | end 12 | 13 | module Spree::GraphQL 14 | describe 'Types::CreditCard' do 15 | let!(:credit_card) { create(:credit_card) { |c| c.cc_type = 'VISA' } } 16 | let!(:variables) {} 17 | let!(:ctx) { { credit_card: credit_card } } 18 | 19 | describe 'brand' do 20 | let!(:query) do 21 | %q{ 22 | query { 23 | testCreditCard { 24 | brand 25 | expiryMonth 26 | expiryYear 27 | firstName 28 | lastDigits 29 | lastName 30 | maskedNumber 31 | } 32 | } 33 | } 34 | end 35 | let!(:result) do 36 | { 37 | data: { 38 | testCreditCard: { 39 | brand: 'VISA', 40 | expiryMonth: credit_card.month.to_i, 41 | expiryYear: credit_card.year.to_i, 42 | lastDigits: credit_card.last_digits, 43 | firstName: 'Spree', 44 | lastName: 'Commerce', 45 | maskedNumber: 'XXXX-XXXX-XXXX-1111' 46 | } 47 | } 48 | # errors: {}, 49 | } 50 | end 51 | 52 | it 'succeeds' do 53 | execute 54 | expect(response_hash).to eq(result_hash) 55 | end 56 | end 57 | 58 | # firstDigits 59 | # @return [Types::String] 60 | describe 'firstDigits' do 61 | let!(:query) do 62 | %q{ 63 | query { 64 | creditCard { 65 | firstDigits 66 | } 67 | } 68 | } 69 | end 70 | let!(:result) do 71 | { 72 | data: { 73 | creditCard: { 74 | firstDigits: 'String' 75 | } 76 | } 77 | # errors: {}, 78 | } 79 | end 80 | # it 'succeeds' do 81 | # execute 82 | # expect(response_hash).to eq(result_hash) 83 | # end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/currency_code_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::CurrencyCode' do 7 | let!(:currency_code) { create(:currency_code) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/mailing_address_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | class Spree::GraphQL::Schema::Types::QueryRoot < Spree::GraphQL::Schema::Types::BaseObject 6 | field :test_address, ::Spree::GraphQL::Schema::Types::MailingAddress, null: false 7 | 8 | def test_address 9 | country = ::Spree::Country.first 10 | province = country.states.find_by(abbr: 'AL') 11 | 12 | ::Spree::Address.new \ 13 | address1: 'Address 1', 14 | address2: 'Address 2', 15 | city: 'City', 16 | company: 'Company Name', 17 | country: country, 18 | firstname: 'John', 19 | lastname: 'Doe', 20 | phone: '0123456789', 21 | state: province, 22 | zipcode: '10000' 23 | end 24 | end 25 | 26 | module Spree::GraphQL 27 | describe 'Types::MailingAddress' do 28 | let!(:state) { create(:state) } 29 | let!(:country) { state.country } 30 | let!(:ctx) { { current_store: current_store } } 31 | let!(:variables) {} 32 | 33 | describe 'fields' do 34 | let!(:query) do 35 | %q{ 36 | query { 37 | testAddress { 38 | address1 39 | address2 40 | city 41 | company 42 | country 43 | countryCodeV2 44 | firstName 45 | lastName 46 | phone 47 | province 48 | provinceCode 49 | zip 50 | formattedArea 51 | name 52 | } 53 | } 54 | } 55 | end 56 | let!(:result) do 57 | { 58 | data: { 59 | testAddress: { 60 | address1: 'Address 1', 61 | address2: 'Address 2', 62 | city: 'City', 63 | company: 'Company Name', 64 | country: country.name, 65 | countryCodeV2: country.iso, 66 | firstName: 'John', 67 | lastName: 'Doe', 68 | phone: '0123456789', 69 | province: state.name, 70 | provinceCode: state.abbr, 71 | zip: '10000', 72 | formattedArea: ['City', state.name, country.name].join(', '), 73 | name: 'John Doe' 74 | } 75 | } 76 | # errors: {}, 77 | } 78 | end 79 | 80 | it 'succeeds' do 81 | execute 82 | expect(response_hash).to eq(result_hash) 83 | end 84 | end 85 | 86 | # formatted: A formatted version of the address, customized by the provided arguments. 87 | # @param with_name [Types::Boolean] (false) 88 | # @param with_company [Types::Boolean] (true) 89 | # @return [[Types::String!]!] 90 | describe 'formatted' do 91 | let!(:query) do 92 | %q{ 93 | query { 94 | mailingAddress { 95 | formatted( 96 | withName: false, 97 | withCompany: true 98 | ) 99 | } 100 | } 101 | } 102 | end 103 | let!(:result) do 104 | { 105 | data: { 106 | mailingAddress: { 107 | formatted: ['String'] 108 | } 109 | } 110 | # errors: {}, 111 | } 112 | end 113 | # it 'succeeds' do 114 | # execute 115 | # expect(response_hash).to eq(result_hash) 116 | # end 117 | end 118 | 119 | # latitude: The latitude coordinate of the customer address. 120 | # @return [Types::Float] 121 | describe 'latitude' do 122 | let!(:query) do 123 | %q{ 124 | query { 125 | mailingAddress { 126 | latitude 127 | } 128 | } 129 | } 130 | end 131 | let!(:result) do 132 | { 133 | data: { 134 | mailingAddress: { 135 | latitude: 'Float' 136 | } 137 | } 138 | # errors: {}, 139 | } 140 | end 141 | # it 'succeeds' do 142 | # execute 143 | # expect(response_hash).to eq(result_hash) 144 | # end 145 | end 146 | 147 | # longitude: The longitude coordinate of the customer address. 148 | # @return [Types::Float] 149 | describe 'longitude' do 150 | let!(:query) do 151 | %q{ 152 | query { 153 | mailingAddress { 154 | longitude 155 | } 156 | } 157 | } 158 | end 159 | let!(:result) do 160 | { 161 | data: { 162 | mailingAddress: { 163 | longitude: 'Float' 164 | } 165 | } 166 | # errors: {}, 167 | } 168 | end 169 | # it 'succeeds' do 170 | # execute 171 | # expect(response_hash).to eq(result_hash) 172 | # end 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/mutation_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::Mutation' do 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/option_type_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::Schema::Types::OptionType do 6 | let(:variant) { create(:variant) } 7 | let(:option_value) { variant.option_values.first } 8 | let(:product) { variant.product } 9 | let(:option_type) { option_value.option_type } 10 | let(:ctx) {} 11 | let(:variables) {} 12 | 13 | describe 'fields' do 14 | let(:query) do 15 | %{ 16 | query { 17 | productBySlug(slug: #{product.slug.to_json}) { 18 | variants { 19 | nodes { 20 | optionValues { 21 | nodes { 22 | optionType { 23 | id 24 | name 25 | presentation 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | end 35 | let(:result) do 36 | { 37 | data: { 38 | productBySlug: { 39 | variants: { 40 | nodes: [{ 41 | optionValues: { 42 | nodes: [{ 43 | optionType: { 44 | id: Spree::GraphQL::Schema.id_from_object(option_type), 45 | name: option_type.name, 46 | presentation: option_type.presentation 47 | } 48 | }] 49 | } 50 | }] 51 | } 52 | } 53 | } 54 | } 55 | end 56 | 57 | it 'succeeds' do 58 | execute 59 | expect(response_hash).to eq(result_hash) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/option_value_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::Schema::Types::OptionValue do 6 | let(:variant) { create(:variant) } 7 | let(:option_value) { variant.option_values.first } 8 | let(:product) { variant.product } 9 | let(:option_type) { option_value.option_type } 10 | let(:ctx) {} 11 | let(:variables) {} 12 | 13 | describe 'fields' do 14 | let(:query) do 15 | %{ 16 | query { 17 | productBySlug(slug: #{product.slug.to_json}) { 18 | variants { 19 | nodes { 20 | optionValues { 21 | nodes { 22 | id 23 | name 24 | optionType { 25 | id 26 | } 27 | presentation 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | end 36 | let(:result) do 37 | { 38 | data: { 39 | productBySlug: { 40 | variants: { 41 | nodes: [{ 42 | optionValues: { 43 | nodes: [{ 44 | id: Spree::GraphQL::Schema.id_from_object(option_value), 45 | name: option_value.name, 46 | optionType: { 47 | id: Spree::GraphQL::Schema.id_from_object(option_type) 48 | }, 49 | presentation: option_value.presentation 50 | }] 51 | } 52 | }] 53 | } 54 | } 55 | } 56 | } 57 | end 58 | 59 | it 'succeeds' do 60 | execute 61 | expect(response_hash).to eq(result_hash) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/payment_settings_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::PaymentSettings' do 7 | let!(:payment_settings) { create(:payment_settings) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | 11 | # acceptedCardBrands: List of the card brands which the shop accepts. 12 | # @return [[Types::CardBrand!]!] 13 | describe 'acceptedCardBrands' do 14 | let!(:query) do 15 | %q{ 16 | query { 17 | paymentSettings { 18 | acceptedCardBrands 19 | } 20 | } 21 | } 22 | end 23 | let!(:result) do 24 | { 25 | data: { 26 | paymentSettings: { 27 | acceptedCardBrands: ['VISA | MASTERCARD | DISCOVER | AMERICAN_EXPRESS | DINERS_CLUB | JCB'] 28 | } 29 | } 30 | # errors: {}, 31 | } 32 | end 33 | # it 'succeeds' do 34 | # execute 35 | # expect(response_hash).to eq(result_hash) 36 | # end 37 | end 38 | 39 | # cardVaultUrl: The url pointing to the endpoint to vault credit cards. 40 | # @return [Types::URL!] 41 | describe 'cardVaultUrl' do 42 | let!(:query) do 43 | %q{ 44 | query { 45 | paymentSettings { 46 | cardVaultUrl 47 | } 48 | } 49 | } 50 | end 51 | let!(:result) do 52 | { 53 | data: { 54 | paymentSettings: { 55 | cardVaultUrl: 'URL' 56 | } 57 | } 58 | # errors: {}, 59 | } 60 | end 61 | # it 'succeeds' do 62 | # execute 63 | # expect(response_hash).to eq(result_hash) 64 | # end 65 | end 66 | 67 | # countryCode: The country where the shop is located. 68 | # @return [Types::CountryCode!] 69 | describe 'countryCode' do 70 | let!(:query) do 71 | %q{ 72 | query { 73 | paymentSettings { 74 | countryCode 75 | } 76 | } 77 | } 78 | end 79 | let!(:result) do 80 | { 81 | data: { 82 | paymentSettings: { 83 | countryCode: 'AF | AX | AL | DZ | AD | AO | AI | AG | AR | AM | AW | AU | AT | AZ | BS | BH | BD | BB | BY | BE | BZ | BJ | BM | BT | BO | BQ | BA | BW | BV | BR | IO | BN | BG | BF | BI | KH | CA | CV | KY | CF | TD | CL | CN | CX | CC | CO | KM | CG | CD | CK | CR | HR | CU | CW | CY | CZ | CI | DK | DJ | DM | DO | EC | EG | SV | GQ | ER | EE | ET | FK | FO | FJ | FI | FR | GF | PF | TF | GA | GM | GE | DE | GH | GI | GR | GL | GD | GP | GT | GG | GN | GW | GY | HT | HM | VA | HN | HK | HU | IS | IN | ID | IR | IQ | IE | IM | IL | IT | JM | JP | JE | JO | KZ | KE | KI | KP | XK | KW | KG | LA | LV | LB | LS | LR | LY | LI | LT | LU | MO | MK | MG | MW | MY | MV | ML | MT | MQ | MR | MU | YT | MX | MD | MC | MN | ME | MS | MA | MZ | MM | NA | NR | NP | NL | AN | NC | NZ | NI | NE | NG | NU | NF | NO | OM | PK | PS | PA | PG | PY | PE | PH | PN | PL | PT | QA | CM | RE | RO | RU | RW | BL | SH | KN | LC | MF | PM | WS | SM | ST | SA | SN | RS | SC | SL | SG | SX | SK | SI | SB | SO | ZA | GS | KR | SS | ES | LK | VC | SD | SR | SJ | SZ | SE | CH | SY | TW | TJ | TZ | TH | TL | TG | TK | TO | TT | TN | TR | TM | TC | TV | UG | UA | AE | GB | US | UM | UY | UZ | VU | VE | VN | VG | WF | EH | YE | ZM | ZW' 84 | } 85 | } 86 | # errors: {}, 87 | } 88 | end 89 | # it 'succeeds' do 90 | # execute 91 | # expect(response_hash).to eq(result_hash) 92 | # end 93 | end 94 | 95 | # currencyCode: The three-letter code for the currency that the shop accepts. 96 | # @return [Types::CurrencyCode!] 97 | describe 'currencyCode' do 98 | let!(:query) do 99 | %q{ 100 | query { 101 | paymentSettings { 102 | currencyCode 103 | } 104 | } 105 | } 106 | end 107 | let!(:result) do 108 | { 109 | data: { 110 | paymentSettings: { 111 | currencyCode: 'USD | EUR | GBP | CAD | AFN | ALL | DZD | AOA | ARS | AMD | AWG | AUD | BBD | AZN | BDT | BSD | BHD | BIF | BYR | BZD | BTN | BAM | BRL | BOB | BWP | BND | BGN | MMK | KHR | CVE | KYD | XAF | CLP | CNY | COP | KMF | CDF | CRC | HRK | CZK | DKK | DOP | XCD | EGP | ETB | XPF | FJD | GMD | GHS | GTQ | GYD | GEL | HTG | HNL | HKD | HUF | ISK | INR | IDR | ILS | IQD | JMD | JPY | JEP | JOD | KZT | KES | KWD | KGS | LAK | LVL | LBP | LSL | LRD | LTL | MGA | MKD | MOP | MWK | MVR | MXN | MYR | MUR | MDL | MAD | MNT | MZN | NAD | NPR | ANG | NZD | NIO | NGN | NOK | OMR | PKR | PGK | PYG | PEN | PHP | PLN | QAR | RON | RUB | RWF | WST | SAR | STD | RSD | SCR | SGD | SDG | SYP | ZAR | KRW | SSP | SBD | LKR | SRD | SZL | SEK | CHF | TWD | THB | TZS | TTD | TND | TRY | TMT | UGX | UAH | AED | UYU | UZS | VUV | VEF | VND | XOF | YER | ZMW' 112 | } 113 | } 114 | # errors: {}, 115 | } 116 | end 117 | # it 'succeeds' do 118 | # execute 119 | # expect(response_hash).to eq(result_hash) 120 | # end 121 | end 122 | 123 | # solidusPaymentsAccountId: The shop’s Solidus Payments account id. 124 | # @return [Types::String] 125 | describe 'solidusPaymentsAccountId' do 126 | let!(:query) do 127 | %q{ 128 | query { 129 | paymentSettings { 130 | solidusPaymentsAccountId 131 | } 132 | } 133 | } 134 | end 135 | let!(:result) do 136 | { 137 | data: { 138 | paymentSettings: { 139 | solidusPaymentsAccountId: 'String' 140 | } 141 | } 142 | # errors: {}, 143 | } 144 | end 145 | # it 'succeeds' do 146 | # execute 147 | # expect(response_hash).to eq(result_hash) 148 | # end 149 | end 150 | 151 | # supportedDigitalWallets: List of the digital wallets which the shop supports. 152 | # @return [[Types::DigitalWallet!]!] 153 | describe 'supportedDigitalWallets' do 154 | let!(:query) do 155 | %q{ 156 | query { 157 | paymentSettings { 158 | supportedDigitalWallets 159 | } 160 | } 161 | } 162 | end 163 | let!(:result) do 164 | { 165 | data: { 166 | paymentSettings: { 167 | supportedDigitalWallets: ['APPLE_PAY | ANDROID_PAY | GOOGLE_PAY | SOLIDUS_PAY'] 168 | } 169 | } 170 | # errors: {}, 171 | } 172 | end 173 | # it 'succeeds' do 174 | # execute 175 | # expect(response_hash).to eq(result_hash) 176 | # end 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/product_collection_sort_keys_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::ProductCollectionSortKeys' do 7 | let!(:product_collection_sort_keys) { create(:product_collection_sort_keys) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/product_sort_keys_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::ProductSortKeys' do 7 | let!(:product_sort_keys) { create(:product_sort_keys) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/product_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::Schema::Types::Product do 6 | let!(:product) do 7 | p = create(:product) 8 | p.description = 'Product description' 9 | p.save! 10 | p 11 | end 12 | let!(:product2) do 13 | p = create(:product) 14 | p.description = nil 15 | p.save! 16 | p 17 | end 18 | let(:ctx) do 19 | { current_store: current_store } 20 | end 21 | let(:variables) {} 22 | 23 | before { create(:store) } 24 | 25 | describe 'fields' do 26 | let(:query) do 27 | %q{ 28 | query { 29 | shop { 30 | products(first: 2) { 31 | edges { 32 | node { 33 | availableOn 34 | createdAt 35 | description 36 | masterVariant { 37 | id 38 | } 39 | name 40 | slug 41 | updatedAt 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | end 49 | let(:result) do 50 | { 51 | data: { 52 | shop: { 53 | products: { 54 | edges: [ 55 | { 56 | node: { 57 | availableOn: product.available_on.iso8601, 58 | createdAt: product.created_at.iso8601, 59 | description: product.description, 60 | masterVariant: { 61 | id: ::Spree::GraphQL::Schema.id_from_object(product.master) 62 | }, 63 | name: product.name, 64 | slug: product.slug, 65 | updatedAt: product.updated_at.iso8601 66 | } 67 | }, 68 | { 69 | node: { 70 | availableOn: product2.available_on.iso8601, 71 | createdAt: product2.created_at.iso8601, 72 | description: product2.description, 73 | masterVariant: { 74 | id: ::Spree::GraphQL::Schema.id_from_object(product2.master) 75 | }, 76 | name: product2.name, 77 | slug: product2.slug, 78 | updatedAt: product2.updated_at.iso8601 79 | } 80 | } 81 | ] 82 | } 83 | } 84 | } 85 | # errors: {}, 86 | } 87 | end 88 | 89 | it 'succeeds' do 90 | execute 91 | expect(response_hash).to eq(result_hash) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/query_root_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::QueryRoot' do 7 | let!(:ctx) { { current_store: current_store } } 8 | let!(:variables) {} 9 | 10 | let!(:products) { create_list(:product, 2) } 11 | 12 | # node 13 | # @param id [Types::ID!] 14 | # @return [Interfaces::Node] 15 | describe 'node' do 16 | let(:product_id) { ::Spree::GraphQL::Schema.id_from_object(products.first) } 17 | let(:variables) { { product_id: product_id } } 18 | let!(:query) do 19 | %q{ 20 | query node($product_id: ID!) { 21 | node(id: $product_id) { 22 | id 23 | } 24 | } 25 | } 26 | end 27 | let!(:result) do 28 | { 29 | data: { 30 | node: { 31 | id: 'To be filled in' 32 | } 33 | } 34 | # errors: {}, 35 | } 36 | end 37 | 38 | it 'succeeds' do 39 | execute 40 | result[:data][:node][:id] = product_id 41 | expect(response_hash).to eq(result_hash) 42 | end 43 | end 44 | 45 | # nodes 46 | # @param ids [[Types::ID!]!] 47 | # @return [[Interfaces::Node]!] 48 | describe 'nodes' do 49 | let(:product_id_1) { ::Spree::GraphQL::Schema.id_from_object(products.first) } 50 | let(:product_id_2) { ::Spree::GraphQL::Schema.id_from_object(products.second) } 51 | let(:variables) { { product_id_1: product_id_1, product_id_2: product_id_2 } } 52 | let!(:query) do 53 | %q{ 54 | query nodes($product_id_1: ID!, $product_id_2: ID!) { 55 | nodes(ids: [$product_id_1, $product_id_2]) { 56 | id 57 | } 58 | } 59 | } 60 | end 61 | let!(:result) do 62 | { 63 | data: { 64 | nodes: [ 65 | { 66 | id: 'To be filled in' 67 | }, 68 | { 69 | id: 'To be filled in' 70 | } 71 | ] 72 | } 73 | # errors: {}, 74 | } 75 | end 76 | 77 | it 'succeeds' do 78 | execute 79 | result[:data][:nodes][0][:id] = product_id_1 80 | result[:data][:nodes][1][:id] = product_id_2 81 | expect(response_hash).to eq(result_hash) 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/shop_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::Shop' do 7 | let!(:shop) do 8 | create :store, 9 | default_currency: 'USD', 10 | cart_tax_country_iso: 'US' 11 | end 12 | let!(:products) { create_list(:product, 10) } 13 | let!(:taxonomies) { create_list(:taxonomy, 10) } 14 | let!(:taxon) do 15 | taxonomy = taxonomies.first 16 | taxon = create :taxon, 17 | name: 'Test taxon', 18 | taxonomy: taxonomy, 19 | products: [products.first] 20 | taxonomy.reload 21 | taxon 22 | end 23 | let(:ctx) { { current_store: current_store } } 24 | let(:variables) {} 25 | 26 | describe 'collectionByHandle' do 27 | let(:variables) { { collection_handle: taxon.permalink } } 28 | let(:query) do 29 | %q{ 30 | query($collection_handle: String!) { 31 | shop { 32 | collectionByHandle(handle: $collection_handle) { 33 | id 34 | } 35 | } 36 | } 37 | } 38 | end 39 | let(:result) do 40 | { 41 | data: { 42 | shop: { 43 | collectionByHandle: { 44 | id: ::Spree::GraphQL::Schema.id_from_object(taxon) 45 | } 46 | } 47 | } 48 | } 49 | end 50 | 51 | it 'succeeds' do 52 | execute 53 | expect(response_hash).to eq(result_hash) 54 | end 55 | end 56 | 57 | describe 'collections' do 58 | let(:query) do 59 | %q{ 60 | query { 61 | shop { 62 | collections( 63 | first: 1 64 | ) { 65 | edges { node { title id } } 66 | pageInfo { hasNextPage hasPreviousPage } 67 | } 68 | } 69 | } 70 | } 71 | end 72 | let(:result) do 73 | { 74 | data: { 75 | shop: { 76 | collections: { 77 | edges: [{ 78 | node: { 79 | id: ::Spree::GraphQL::Schema.id_from_object(taxonomies.first), 80 | title: taxonomies.first.name 81 | } 82 | }], 83 | pageInfo: { 84 | hasNextPage: true, 85 | hasPreviousPage: false 86 | } 87 | } 88 | } 89 | } 90 | } 91 | end 92 | 93 | it 'succeeds' do 94 | execute 95 | expect(response_hash).to eq(result_hash) 96 | end 97 | 98 | describe 'reverse' do 99 | let(:query) do 100 | %q{ 101 | query { 102 | shop { 103 | collections( 104 | first: 1 105 | reverse: true, 106 | ) { 107 | edges { node { title id } } 108 | pageInfo { hasNextPage hasPreviousPage } 109 | } 110 | } 111 | } 112 | } 113 | end 114 | let(:result) do 115 | { 116 | data: { 117 | shop: { 118 | collections: { 119 | edges: [{ 120 | node: { 121 | id: ::Spree::GraphQL::Schema.id_from_object(taxonomies.last), 122 | title: taxonomies.last.name 123 | } 124 | }], 125 | pageInfo: { 126 | hasNextPage: true, 127 | hasPreviousPage: false 128 | } 129 | } 130 | } 131 | } 132 | } 133 | end 134 | 135 | it 'succeeds' do 136 | execute 137 | expect(response_hash).to eq(result_hash) 138 | end 139 | end 140 | 141 | describe 'sortKey' do 142 | describe 'updatedAt' do 143 | let(:query) do 144 | %q{ 145 | query { 146 | shop { 147 | collections( last: 1 reverse: false, sortKey: UPDATED_AT) { 148 | edges { node { updatedAt } } 149 | pageInfo { hasNextPage hasPreviousPage } 150 | } 151 | } 152 | } 153 | } 154 | end 155 | let(:result) do 156 | { 157 | data: { 158 | shop: { 159 | collections: { 160 | edges: [{ 161 | node: { 162 | updatedAt: taxonomies.max_by(&:updated_at).updated_at.iso8601 163 | } 164 | }], 165 | pageInfo: { 166 | hasNextPage: false, 167 | hasPreviousPage: true 168 | } 169 | } 170 | } 171 | } 172 | } 173 | end 174 | 175 | it 'succeeds' do 176 | execute 177 | expect(response_hash).to eq(result_hash) 178 | end 179 | end 180 | 181 | describe 'title' do 182 | let(:query) do 183 | %q{ 184 | query { 185 | shop { 186 | collections( first: 1 reverse: false, sortKey: TITLE) { 187 | edges { node { title } } 188 | pageInfo { hasNextPage hasPreviousPage } 189 | } 190 | } 191 | } 192 | } 193 | end 194 | let(:result) do 195 | { 196 | data: { 197 | shop: { 198 | collections: { 199 | edges: [{ 200 | node: { 201 | title: taxonomies.min_by(&:name).name 202 | } 203 | }], 204 | pageInfo: { 205 | hasNextPage: true, 206 | hasPreviousPage: false 207 | } 208 | } 209 | } 210 | } 211 | } 212 | end 213 | 214 | it 'succeeds' do 215 | execute 216 | expect(response_hash).to eq(result_hash) 217 | end 218 | end 219 | 220 | describe 'id' do 221 | let(:query) do 222 | %q{ 223 | query { 224 | shop { 225 | collections( first: 1 reverse: true, sortKey: ID) { 226 | edges { node { id } } 227 | pageInfo { hasNextPage hasPreviousPage } 228 | } 229 | } 230 | } 231 | } 232 | end 233 | let(:result) do 234 | { 235 | data: { 236 | shop: { 237 | collections: { 238 | edges: [{ 239 | node: { 240 | id: ::Spree::GraphQL::Schema.id_from_object(taxonomies.max_by(&:id)) 241 | } 242 | }], 243 | pageInfo: { 244 | hasNextPage: true, 245 | hasPreviousPage: false 246 | } 247 | } 248 | } 249 | } 250 | } 251 | end 252 | 253 | it 'succeeds' do 254 | execute 255 | expect(response_hash).to eq(result_hash) 256 | end 257 | end 258 | end 259 | end 260 | 261 | describe 'description' do 262 | let(:description) { 'Sample Store Description' } 263 | let(:query) do 264 | %q{ 265 | query { 266 | shop { 267 | description 268 | } 269 | } 270 | } 271 | end 272 | let(:result) do 273 | { 274 | data: { 275 | shop: { 276 | description: description 277 | } 278 | } 279 | } 280 | end 281 | 282 | it 'succeeds' do 283 | ctx[:current_store].meta_description = description 284 | execute 285 | expect(response_hash).to eq(result_hash) 286 | end 287 | end 288 | 289 | describe 'moneyFormat' do 290 | let(:query) do 291 | %q{ 292 | query { 293 | shop { 294 | moneyFormat 295 | } 296 | } 297 | } 298 | end 299 | let(:result) do 300 | { 301 | data: { 302 | shop: { 303 | moneyFormat: '${{amount}}' 304 | } 305 | } 306 | } 307 | end 308 | 309 | it 'succeeds' do 310 | execute 311 | expect(response_hash).to eq(result_hash) 312 | end 313 | end 314 | 315 | describe 'name' do 316 | let(:query) do 317 | %q{ 318 | query { 319 | shop { 320 | name 321 | } 322 | } 323 | } 324 | end 325 | let(:result) do 326 | { 327 | data: { 328 | shop: { 329 | name: shop.name 330 | } 331 | } 332 | } 333 | end 334 | 335 | it 'succeeds' do 336 | execute 337 | expect(response_hash).to eq(result_hash) 338 | end 339 | end 340 | 341 | describe 'paymentSettings' do 342 | let(:query) do 343 | %q{ 344 | query { 345 | shop { 346 | paymentSettings { 347 | #acceptedCardBrands 348 | #cardVaultUrl 349 | countryCode 350 | currencyCode 351 | #solidusPaymentsAccountId 352 | #supportedDigitalWallets 353 | } 354 | } 355 | } 356 | } 357 | end 358 | let(:result) do 359 | { 360 | data: { 361 | shop: { 362 | paymentSettings: { 363 | # acceptedCardBrands: 'VISA | MASTERCARD | DISCOVER | AMERICAN_EXPRESS | DINERS_CLUB | JCB', 364 | # cardVaultUrl: 'URL', 365 | countryCode: 'US', 366 | currencyCode: 'USD' 367 | # solidusPaymentsAccountId: 'String', 368 | # supportedDigitalWallets: 'APPLE_PAY | ANDROID_PAY | GOOGLE_PAY | SOLIDUS_PAY', 369 | } 370 | } 371 | } 372 | } 373 | end 374 | 375 | it 'succeeds' do 376 | execute 377 | expect(response_hash).to eq(result_hash) 378 | end 379 | end 380 | 381 | describe 'primaryDomain' do 382 | let(:query) do 383 | %q{ 384 | query { 385 | shop { 386 | primaryDomain { 387 | host 388 | sslEnabled 389 | url 390 | } 391 | } 392 | } 393 | } 394 | end 395 | let(:result) do 396 | { 397 | data: { 398 | shop: { 399 | primaryDomain: { 400 | host: ctx[:current_store].url, 401 | sslEnabled: false, 402 | url: "http://#{ctx[:current_store].url}" 403 | } 404 | } 405 | } 406 | } 407 | end 408 | 409 | it 'succeeds' do 410 | execute 411 | expect(response_hash).to eq(result_hash) 412 | end 413 | end 414 | 415 | describe 'productByHandle' do 416 | let(:variables) { { product_handle: products.first.slug } } 417 | let(:query) do 418 | %q{ 419 | query($product_handle: String!) { 420 | shop { 421 | productByHandle(handle: $product_handle) { 422 | id 423 | name 424 | } 425 | } 426 | } 427 | } 428 | end 429 | let(:result) do 430 | { 431 | data: { 432 | shop: { 433 | productByHandle: { 434 | id: ::Spree::GraphQL::Schema.id_from_object(products.first), 435 | name: products.first.name 436 | } 437 | } 438 | } 439 | } 440 | end 441 | 442 | it 'succeeds' do 443 | execute 444 | expect(response_hash).to eq(result_hash) 445 | end 446 | end 447 | 448 | describe 'products' do 449 | let(:query) do 450 | %q{ 451 | query { 452 | shop { 453 | products( 454 | first: 1 455 | ) { 456 | edges { 457 | node { 458 | id 459 | slug 460 | } 461 | } 462 | pageInfo { hasNextPage hasPreviousPage } 463 | } 464 | } 465 | } 466 | } 467 | end 468 | let(:result) do 469 | { 470 | data: { 471 | shop: { 472 | products: { 473 | edges: [{ 474 | node: { 475 | id: ::Spree::GraphQL::Schema.id_from_object(products.first), 476 | slug: products.first.slug 477 | } 478 | }], 479 | pageInfo: { 480 | hasNextPage: true, 481 | hasPreviousPage: false 482 | } 483 | } 484 | } 485 | } 486 | } 487 | end 488 | 489 | it 'succeeds' do 490 | execute 491 | expect(response_hash).to eq(result_hash) 492 | end 493 | 494 | describe 'reverse' do 495 | let(:query) do 496 | %q{ 497 | query { 498 | shop { 499 | products( 500 | first: 1 501 | reverse: true, 502 | ) { 503 | edges { 504 | node { 505 | id 506 | slug 507 | } 508 | } 509 | pageInfo { hasNextPage hasPreviousPage } 510 | } 511 | } 512 | } 513 | } 514 | end 515 | let(:result) do 516 | { 517 | data: { 518 | shop: { 519 | products: { 520 | edges: [{ 521 | node: { 522 | id: ::Spree::GraphQL::Schema.id_from_object(products.last), 523 | slug: products.last.slug 524 | } 525 | }], 526 | pageInfo: { 527 | hasNextPage: true, 528 | hasPreviousPage: false 529 | } 530 | } 531 | } 532 | } 533 | } 534 | end 535 | 536 | it 'succeeds' do 537 | execute 538 | expect(response_hash).to eq(result_hash) 539 | end 540 | end 541 | 542 | describe 'sortKey' do 543 | describe 'createdAt' do 544 | let(:query) do 545 | %q{ 546 | query { 547 | shop { 548 | products( last: 1 reverse: false, sortKey: CREATED_AT) { 549 | edges { node { createdAt } } 550 | pageInfo { hasNextPage hasPreviousPage } 551 | } 552 | } 553 | } 554 | } 555 | end 556 | let(:result) do 557 | { 558 | data: { 559 | shop: { 560 | products: { 561 | edges: [{ 562 | node: { 563 | createdAt: products.max_by(&:created_at).created_at.iso8601 564 | } 565 | }], 566 | pageInfo: { 567 | hasNextPage: false, 568 | hasPreviousPage: true 569 | } 570 | } 571 | } 572 | } 573 | } 574 | end 575 | 576 | it 'succeeds' do 577 | execute 578 | expect(response_hash).to eq(result_hash) 579 | end 580 | end 581 | 582 | describe 'updatedAt' do 583 | let(:query) do 584 | %q{ 585 | query { 586 | shop { 587 | products( last: 1 reverse: false, sortKey: UPDATED_AT) { 588 | edges { node { updatedAt } } 589 | pageInfo { hasNextPage hasPreviousPage } 590 | } 591 | } 592 | } 593 | } 594 | end 595 | let(:result) do 596 | { 597 | data: { 598 | shop: { 599 | products: { 600 | edges: [{ 601 | node: { 602 | updatedAt: products.max_by(&:updated_at).updated_at.iso8601 603 | } 604 | }], 605 | pageInfo: { 606 | hasNextPage: false, 607 | hasPreviousPage: true 608 | } 609 | } 610 | } 611 | } 612 | } 613 | end 614 | 615 | it 'succeeds' do 616 | execute 617 | expect(response_hash).to eq(result_hash) 618 | end 619 | end 620 | 621 | describe 'title' do 622 | let(:query) do 623 | %q{ 624 | query { 625 | shop { 626 | products( first: 1 reverse: false, sortKey: NAME) { 627 | edges { 628 | node { 629 | name 630 | } 631 | } 632 | pageInfo { hasNextPage hasPreviousPage } 633 | } 634 | } 635 | } 636 | } 637 | end 638 | let(:result) do 639 | { 640 | data: { 641 | shop: { 642 | products: { 643 | edges: [{ 644 | node: { 645 | name: products.min_by(&:name).name 646 | } 647 | }], 648 | pageInfo: { 649 | hasNextPage: true, 650 | hasPreviousPage: false 651 | } 652 | } 653 | } 654 | } 655 | } 656 | end 657 | 658 | it 'succeeds' do 659 | execute 660 | expect(response_hash).to eq(result_hash) 661 | end 662 | end 663 | 664 | describe 'id' do 665 | let(:query) do 666 | %q{ 667 | query { 668 | shop { 669 | products( first: 1 reverse: true, sortKey: ID) { 670 | edges { node { id } } 671 | pageInfo { hasNextPage hasPreviousPage } 672 | } 673 | } 674 | } 675 | } 676 | end 677 | let(:result) do 678 | { 679 | data: { 680 | shop: { 681 | products: { 682 | edges: [{ 683 | node: { 684 | id: ::Spree::GraphQL::Schema.id_from_object(products.max_by(&:id)) 685 | } 686 | }], 687 | pageInfo: { 688 | hasNextPage: true, 689 | hasPreviousPage: false 690 | } 691 | } 692 | } 693 | } 694 | } 695 | end 696 | 697 | it 'succeeds' do 698 | execute 699 | expect(response_hash).to eq(result_hash) 700 | end 701 | end 702 | end 703 | end 704 | 705 | describe 'shipsToCountries' do 706 | let(:available_countries_isos) { %w[IT US] } 707 | let(:available_countries) { instance_double('available_countries', map: available_countries_isos) } 708 | let(:ctx) do 709 | { 710 | current_store: current_store, 711 | helpers: instance_double('helpers') 712 | } 713 | end 714 | let(:query) do 715 | %q{ 716 | query { 717 | shop { 718 | shipsToCountries 719 | } 720 | } 721 | } 722 | end 723 | let(:result) do 724 | { 725 | data: { 726 | shop: { 727 | shipsToCountries: available_countries_isos 728 | } 729 | } 730 | } 731 | end 732 | 733 | it 'succeeds' do 734 | expect(ctx[:helpers]).to receive(:available_countries).once.with(no_args).and_return(available_countries) 735 | execute 736 | expect(response_hash).to eq(result_hash) 737 | end 738 | end 739 | end 740 | end 741 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/url_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | module Spree::GraphQL 6 | describe 'Types::URL' do 7 | let!(:url) { create(:url) } 8 | let!(:ctx) { { current_store: current_store } } 9 | let!(:variables) {} 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spree/graphql/schema/types/variant_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Spree::GraphQL::Schema::Types::Variant do 6 | let(:option_values) { Array.new(2) { |n| create(:option_value, position: n + 1) } } 7 | let!(:product) do 8 | p = create(:product) 9 | p.slug = 'product' 10 | p.save 11 | p 12 | end 13 | let!(:variant) do 14 | v = build(:variant) 15 | v.product = product 16 | v.weight = 5.84 17 | v.option_values = option_values 18 | v.save 19 | v 20 | end 21 | let(:ctx) {} 22 | let(:variables) {} 23 | 24 | describe 'fields' do 25 | let(:query) do 26 | %{ 27 | query { 28 | productBySlug(slug: #{product.slug.to_json}) { 29 | id 30 | variants { 31 | nodes { 32 | id 33 | sku 34 | title 35 | weight 36 | optionValues { 37 | nodes { 38 | id 39 | } 40 | } 41 | product { 42 | id 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | end 50 | let(:expected_option_values) do 51 | option_values 52 | .sort_by(&:position) 53 | .map { |option_value| { id: ::Spree::GraphQL::Schema.id_from_object(option_value) } } 54 | end 55 | let(:result) do 56 | { 57 | data: { 58 | productBySlug: { 59 | id: ::Spree::GraphQL::Schema.id_from_object(product), 60 | variants: { 61 | nodes: [{ 62 | id: ::Spree::GraphQL::Schema.id_from_object(variant), 63 | sku: variant.sku, 64 | title: variant.name, 65 | weight: variant.weight, 66 | optionValues: { 67 | nodes: expected_option_values 68 | }, 69 | product: { 70 | id: ::Spree::GraphQL::Schema.id_from_object(product) 71 | } 72 | }] 73 | } 74 | } 75 | } 76 | } 77 | end 78 | 79 | it 'succeeds' do 80 | execute 81 | expect(response_hash).to eq(result_hash) 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/support/graphql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Spree::GraphQL::Spec 4 | module Helpers 5 | def current_store 6 | ::Spree::Store.where(default: true).first 7 | end 8 | 9 | # Parameters accepted are GraphQL's Query#initialize: 10 | # (query_string||query()), context, variables, operation_name, root_value, max_depth, max_complexity, except, only 11 | def execute(action = nil, params = {}) 12 | unless action 13 | raise(Exception, 'Must pass a query to run as argument or define let!(:query) { ... }') unless respond_to?(:query) 14 | 15 | action = query 16 | end 17 | 18 | params[:context] ||= ctx 19 | params[:variables] ||= variables 20 | 21 | @response = ::Spree::GraphQL::Schema.execute(action, params) 22 | end 23 | 24 | def response 25 | @response 26 | end 27 | 28 | def response_json(query_response = response) 29 | query_response.to_json 30 | end 31 | 32 | def response_hash(query_response = response) 33 | JSON.parse(query_response.to_json).with_indifferent_access 34 | end 35 | 36 | def result_json(query_result = result) 37 | query_result.is_a?(Hash) ? query_result.to_json : query_result 38 | end 39 | 40 | def result_hash(query_result = result) 41 | (query_result.is_a?(Hash) ? query_result : JSON.parse(query_result.to_json)).with_indifferent_access 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/shared_contexts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.shared_context 'when the lazy resolver adapter is BatchLoader' do 4 | before do 5 | SolidusGraphqlApi::Config.lazy_resolver_adapter_class_name = 6 | 'Spree::GraphQL::LazyResolver::Adapters::BatchLoaderAdapter' 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/support/spree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # require 'spree/testing_support/factories' 4 | # require 'spree/testing_support/url_helpers' 5 | require 'spree/testing_support/controller_requests' 6 | # require 'spree/testing_support/authorization_helpers' 7 | # require 'spree/testing_support/capybara_ext' 8 | 9 | RSpec.configure do |config| 10 | # config.include Spree::TestingSupport::UrlHelpers 11 | config.include Spree::TestingSupport::ControllerRequests, type: :controller 12 | end 13 | --------------------------------------------------------------------------------