From 5b88865de8f5ce6f23237febd7991eb8a7c7d9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Tue, 3 Aug 2021 20:25:26 -0300 Subject: [PATCH] add graphql endpoint --- Gemfile | 8 ++-- Gemfile.lock | 5 +++ app/controllers/graphql_controller.rb | 51 ++++++++++++++++++++++++++ app/graphql/mutations/.keep | 0 app/graphql/mutations/base_mutation.rb | 9 +++++ app/graphql/types/.keep | 0 app/graphql/types/base_argument.rb | 5 +++ app/graphql/types/base_connection.rb | 7 ++++ app/graphql/types/base_edge.rb | 7 ++++ app/graphql/types/base_enum.rb | 5 +++ app/graphql/types/base_field.rb | 6 +++ app/graphql/types/base_input_object.rb | 6 +++ app/graphql/types/base_interface.rb | 10 +++++ app/graphql/types/base_object.rb | 8 ++++ app/graphql/types/base_scalar.rb | 5 +++ app/graphql/types/base_union.rb | 7 ++++ app/graphql/types/mutation_type.rb | 11 ++++++ app/graphql/types/node_type.rb | 8 ++++ app/graphql/types/query_type.rb | 18 +++++++++ app/graphql/x_stake_schema.rb | 18 +++++++++ config/routes.rb | 9 +++-- 21 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 app/controllers/graphql_controller.rb create mode 100644 app/graphql/mutations/.keep create mode 100644 app/graphql/mutations/base_mutation.rb create mode 100644 app/graphql/types/.keep create mode 100644 app/graphql/types/base_argument.rb create mode 100644 app/graphql/types/base_connection.rb create mode 100644 app/graphql/types/base_edge.rb create mode 100644 app/graphql/types/base_enum.rb create mode 100644 app/graphql/types/base_field.rb create mode 100644 app/graphql/types/base_input_object.rb create mode 100644 app/graphql/types/base_interface.rb create mode 100644 app/graphql/types/base_object.rb create mode 100644 app/graphql/types/base_scalar.rb create mode 100644 app/graphql/types/base_union.rb create mode 100644 app/graphql/types/mutation_type.rb create mode 100644 app/graphql/types/node_type.rb create mode 100644 app/graphql/types/query_type.rb create mode 100644 app/graphql/x_stake_schema.rb diff --git a/Gemfile b/Gemfile index 9f93314..bc0248e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,16 +12,17 @@ gem "rails", "~> 6.1.4" gem "sass-rails", ">= 6" gem "turbolinks", "~> 5" gem "webpacker", "~> 5.0" - gem "bootsnap", ">= 1.4.4", require: false -gem "administrate" - gem "devise" gem "devise-bootstrap-views", "~> 1.0" gem "devise-i18n" +gem "administrate" +gem "graphql" + group :development, :test do + gem "graphql_playground-rails" gem "pry-byebug", platforms: [:mri, :mingw, :x64_mingw] gem "capybara" gem "rspec-rails" @@ -32,6 +33,7 @@ group :development, :test do end group :development do + gem "graphql_playground-rails" gem "web-console", ">= 4.1.0" gem "listen", "~> 3.3" diff --git a/Gemfile.lock b/Gemfile.lock index cea1aaf..36ad6f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,9 @@ GEM ffi (1.15.3) globalid (0.5.2) activesupport (>= 5.0) + graphql (1.12.14) + graphql_playground-rails (2.1.0) + rails (>= 5.1.0) i18n (1.8.10) concurrent-ruby (~> 1.0) jbuilder (2.11.2) @@ -299,6 +302,8 @@ DEPENDENCIES devise devise-bootstrap-views (~> 1.0) devise-i18n + graphql + graphql_playground-rails jbuilder (~> 2.7) listen (~> 3.3) pg (~> 1.1) diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb new file mode 100644 index 0000000..c161a37 --- /dev/null +++ b/app/controllers/graphql_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +class GraphqlController < ApplicationController + # If accessing from outside this domain, nullify the session + # This allows for outside API access while preventing CSRF attacks, + # but you'll have to authenticate your user separately + # protect_from_forgery with: :null_session + + def execute + variables = prepare_variables(params[:variables]) + query = params[:query] + operation_name = params[:operationName] + context = { + # Query context goes here, for example: + current_user: current_admin_user, + } + result = XStakeSchema.execute(query, variables: variables, context: context, operation_name: operation_name) + render(json: result) + rescue StandardError => e + raise e unless Rails.env.development? + handle_error_in_development(e) + end + + private + + # Handle variables in form data, JSON body, or a blank value + def prepare_variables(variables_param) + case variables_param + when String + if variables_param.present? + JSON.parse(variables_param) || {} + else + {} + end + when Hash + variables_param + when ActionController::Parameters + variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. + when nil + {} + else + raise ArgumentError, "Unexpected parameter: #{variables_param}" + end + end + + def handle_error_in_development(e) + logger.error(e.message) + logger.error(e.backtrace.join("\n")) + + render(json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: :internal_server_error) + end +end diff --git a/app/graphql/mutations/.keep b/app/graphql/mutations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb new file mode 100644 index 0000000..a6576af --- /dev/null +++ b/app/graphql/mutations/base_mutation.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Mutations + class BaseMutation < GraphQL::Schema::RelayClassicMutation + argument_class Types::BaseArgument + field_class Types::BaseField + input_object_class Types::BaseInputObject + object_class Types::BaseObject + end +end diff --git a/app/graphql/types/.keep b/app/graphql/types/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb new file mode 100644 index 0000000..3b7013d --- /dev/null +++ b/app/graphql/types/base_argument.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module Types + class BaseArgument < GraphQL::Schema::Argument + end +end diff --git a/app/graphql/types/base_connection.rb b/app/graphql/types/base_connection.rb new file mode 100644 index 0000000..c929820 --- /dev/null +++ b/app/graphql/types/base_connection.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Types + class BaseConnection < Types::BaseObject + # add `nodes` and `pageInfo` fields, as well as `edge_type(...)` and `node_nullable(...)` overrides + include GraphQL::Types::Relay::ConnectionBehaviors + end +end diff --git a/app/graphql/types/base_edge.rb b/app/graphql/types/base_edge.rb new file mode 100644 index 0000000..bfa77ee --- /dev/null +++ b/app/graphql/types/base_edge.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Types + class BaseEdge < Types::BaseObject + # add `node` and `cursor` fields, as well as `node_type(...)` override + include GraphQL::Types::Relay::EdgeBehaviors + end +end diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb new file mode 100644 index 0000000..f855d15 --- /dev/null +++ b/app/graphql/types/base_enum.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module Types + class BaseEnum < GraphQL::Schema::Enum + end +end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb new file mode 100644 index 0000000..cc277ea --- /dev/null +++ b/app/graphql/types/base_field.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Types + class BaseField < GraphQL::Schema::Field + argument_class Types::BaseArgument + end +end diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb new file mode 100644 index 0000000..527d02d --- /dev/null +++ b/app/graphql/types/base_input_object.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Types + class BaseInputObject < GraphQL::Schema::InputObject + argument_class Types::BaseArgument + end +end diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb new file mode 100644 index 0000000..3cee93d --- /dev/null +++ b/app/graphql/types/base_interface.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Types + module BaseInterface + include GraphQL::Schema::Interface + edge_type_class(Types::BaseEdge) + connection_type_class(Types::BaseConnection) + + field_class Types::BaseField + end +end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb new file mode 100644 index 0000000..fc78734 --- /dev/null +++ b/app/graphql/types/base_object.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Types + class BaseObject < GraphQL::Schema::Object + edge_type_class(Types::BaseEdge) + connection_type_class(Types::BaseConnection) + field_class Types::BaseField + end +end diff --git a/app/graphql/types/base_scalar.rb b/app/graphql/types/base_scalar.rb new file mode 100644 index 0000000..b2c1af2 --- /dev/null +++ b/app/graphql/types/base_scalar.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module Types + class BaseScalar < GraphQL::Schema::Scalar + end +end diff --git a/app/graphql/types/base_union.rb b/app/graphql/types/base_union.rb new file mode 100644 index 0000000..f4c0671 --- /dev/null +++ b/app/graphql/types/base_union.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Types + class BaseUnion < GraphQL::Schema::Union + edge_type_class(Types::BaseEdge) + connection_type_class(Types::BaseConnection) + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb new file mode 100644 index 0000000..1c6dc93 --- /dev/null +++ b/app/graphql/types/mutation_type.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +module Types + class MutationType < Types::BaseObject + # TODO: remove me + field :test_field, String, null: false, + description: "An example field added by the generator" + def test_field + "Hello World" + end + end +end diff --git a/app/graphql/types/node_type.rb b/app/graphql/types/node_type.rb new file mode 100644 index 0000000..8585821 --- /dev/null +++ b/app/graphql/types/node_type.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Types + module NodeType + include Types::BaseInterface + # Add the `id` field + include GraphQL::Types::Relay::NodeBehaviors + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb new file mode 100644 index 0000000..bc0863e --- /dev/null +++ b/app/graphql/types/query_type.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +module Types + class QueryType < Types::BaseObject + # Add `node(id: ID!) and `nodes(ids: [ID!]!)` + include GraphQL::Types::Relay::HasNodeField + include GraphQL::Types::Relay::HasNodesField + + # Add root-level fields here. + # They will be entry points for queries on your schema. + + # TODO: remove me + field :test_field, String, null: false, + description: "An example field added by the generator" + def test_field + context[:current_user].email + end + end +end diff --git a/app/graphql/x_stake_schema.rb b/app/graphql/x_stake_schema.rb new file mode 100644 index 0000000..f07976a --- /dev/null +++ b/app/graphql/x_stake_schema.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +class XStakeSchema < GraphQL::Schema + mutation(Types::MutationType) + query(Types::QueryType) + + def self.resolve_type(abstract_type, obj, ctx) + raise(GraphQL::RequiredImplementationMissingError) + end + + def self.id_from_object(object, type_definition, query_ctx) + GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id) + end + + def self.object_from_id(id, query_ctx) + type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id) + type_name.constantize.find(item_id) + end +end diff --git a/config/routes.rb b/config/routes.rb index 3d66cef..bc6ec78 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true Rails.application.routes.draw do + mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" if Rails.env.development? + + post "/graphql", to: "graphql#execute" + + devise_for :admin_users + namespace :admin do resources :admin_users root to: "admin_users#index" end - - devise_for :admin_users - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end