-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/ Beta support for graphql #330
Changes from 16 commits
f0c627f
df4e577
17d2bd7
36fbfcb
94dc120
ba23747
c62a8ba
ab1a69c
1d13daa
cb5a4f0
6edc88b
a240f50
e009a76
1d19f49
dcdfbec
cc0efe1
a9b44fb
36bfd87
31775e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
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 = { current_user: get_current_user } | ||
result = GqlSampleSchema.execute(query, variables: variables, context: context, operation_name: operation_name) | ||
render json: result | ||
rescue => 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: 500 | ||
end | ||
|
||
def get_current_user | ||
if request.headers['Authorization'] | ||
_, token = request.headers['Authorization'].split | ||
decoded_token = JWT.decode token, ENV['HMAC_SECRET'], true, { algorithm: 'HS256' } | ||
User.find(decoded_token.first["id"]) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
require 'jwt' | ||
|
||
class Mutations::LoginMutation < Mutations::BaseMutation | ||
null true | ||
|
||
# argument :user_id, ID, required: true, loads: Types::UserType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no caché esto There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. se me pasó jeje |
||
argument :email, String, required: true | ||
argument :password, String, required: true | ||
|
||
|
||
field :token, String, null: true | ||
|
||
def resolve(email:, password:) | ||
user = User.find_by(email: email) | ||
if user&.valid_password?(password) | ||
payload = { id: user.id, email: user.email, exp: (Time.zone.now + 24.hours).to_i } | ||
token = JWT.encode payload, ENV['HMAC_SECRET'], 'HS256' | ||
return { token: token } | ||
end | ||
GraphQL::ExecutionError.new("User or Password invalid") | ||
rescue ActiveRecord::RecordInvalid => e | ||
GraphQL::ExecutionError.new("Invalid input: #{e.record.errors.full_messages.join(', ')}") | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Queries | ||
class BaseQuery < GraphQL::Schema::Resolver | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Types::Base | ||
class BaseArgument < GraphQL::Schema::Argument | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Types::Base | ||
class BaseEnum < GraphQL::Schema::Enum | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module Types::Base | ||
class BaseField < GraphQL::Schema::Field | ||
argument_class Types::Base::BaseArgument | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module Types::Base | ||
class BaseInputObject < GraphQL::Schema::InputObject | ||
argument_class Types::Base::BaseArgument | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module Types::Base | ||
module BaseInterface | ||
include GraphQL::Schema::Interface | ||
|
||
field_class Types::Base::BaseField | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module Types::Base | ||
class BaseObject < GraphQL::Schema::Object | ||
field_class Types::Base::BaseField | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Types::Base | ||
class BaseScalar < GraphQL::Schema::Scalar | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module Types::Base | ||
class BaseUnion < GraphQL::Schema::Union | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module Types | ||
class MutationType < Types::Base::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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module Types | ||
class QueryType < Types::Base::BaseObject | ||
# 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 | ||
"Hello World!" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# config/initializers/graphql_playground.rb | ||
# All config options have a default that should work out of the box | ||
GraphqlPlayground::Rails.configure do |config| | ||
# config.headers = { | ||
# 'X-Auth-Header' => ->(view_context) { "123" } | ||
# } | ||
# config.title = "Playground" | ||
# config.csrf = true | ||
# config.playground_version = "latest" | ||
# # Ideally the assets would be added to your projects `vendor/assets` directories | ||
# config.favicon = "/assets/playground.ico" | ||
# config.playground_js_url = "/assets/playground.js" | ||
# config.playground_css_url = "/assets/playground.css" | ||
# # see: https://github.com/prisma-labs/graphql-playground#settings | ||
config.settings = { | ||
"schema.polling.enable": false | ||
} | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,36 @@ | ||
class Recipes::Api < Rails::AppBuilder | ||
def ask | ||
api_support = answer(:api) { Ask.confirm("Do you want to enable API support?") } | ||
set(:api_support, api_support) | ||
api_interfaces = { | ||
rest: "REST (with Power API)", | ||
graphql: "GraphQL (beta)", | ||
none: "None, thanks" | ||
} | ||
api_interface = answer(:api) do | ||
api_interfaces.keys[Ask.list("Which API interface are you using?", api_interfaces.values)] | ||
end | ||
set :api, api_interface.to_sym | ||
end | ||
|
||
def create | ||
add_api if get(:api_support) | ||
if get(:api) == :graphql | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pregunta inocente: tendrá sentido tenerlo como recetas aparte? Digo, existe la posibilidad de tener un proyecto usando rest para algunas cosas y graphql para otras? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. si es cierto no lo había pensado. |
||
add_graphql | ||
elsif get(:api) == :rest | ||
add_power_api | ||
end | ||
end | ||
|
||
def install | ||
add_api | ||
ask | ||
create | ||
end | ||
|
||
def installed? | ||
gem_exists?(/power_api/) | ||
gem_exists?(/power_api/) || gem_exists?(/graphql/) | ||
end | ||
|
||
private | ||
|
||
def add_api | ||
def add_power_api | ||
gather_gem 'power_api' | ||
|
||
gather_gems(:development, :test) do | ||
|
@@ -31,4 +43,72 @@ def add_api | |
generate "power_api:install" | ||
end | ||
end | ||
|
||
def add_graphql | ||
gather_gem 'graphql' | ||
if get(:authentication) | ||
gather_gem 'jwt' | ||
end | ||
gather_gems(:development, :test) do | ||
gather_gem 'graphql_playground-rails' | ||
end | ||
|
||
after(:gem_install) do | ||
generate "graphql:install --skip_graphiql" | ||
playground_route = <<~HEREDOC | ||
\n | ||
if Rails.env.development? | ||
mount GraphqlPlayground::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" | ||
end | ||
HEREDOC | ||
inject_into_file( | ||
'config/routes.rb', | ||
playground_route, | ||
after: 'post "/graphql", to: "graphql#execute"' | ||
) | ||
ironcadiz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
copy_file( | ||
"../assets/config/graphql_playground.rb", | ||
"config/initializers/graphql_playground.rb" | ||
) | ||
remove_dir 'app/graphql/types' | ||
directory '../assets/app/graphql/types', 'app/graphql/types' | ||
gsub_file 'app/graphql/mutations/base_mutation.rb', 'Types::Base', 'Types::Base::Base' | ||
directory '../assets/app/graphql/queries', 'app/graphql/queries' | ||
gsub_file 'app/graphql/mutations/base_mutation.rb', 'RelayClassic', '' | ||
gsub_file( | ||
'app/graphql/mutations/base_mutation.rb', | ||
" input_object_class Types::Base::BaseInputObject\n", '' | ||
) | ||
|
||
if get(:authentication) | ||
copy_file( | ||
'../assets/app/graphql/graphql_controller.rb', | ||
'app/controllers/graphql_controller.rb', | ||
force: true | ||
) | ||
gsub_file( | ||
'app/controllers/graphql_controller.rb', | ||
'GqlSampleSchema', | ||
"#{get(:titleized_app_name).delete(' ')}Schema" | ||
) | ||
copy_file( | ||
'../assets/app/graphql/mutations/login_mutation.rb', | ||
'app/graphql/mutations/login_mutation.rb' | ||
) | ||
inject_into_file( | ||
'app/graphql/types/mutation_type.rb', | ||
"\n field :login, mutation: Mutations::LoginMutation", | ||
after: 'class MutationType < Types::Base::BaseObject' | ||
) | ||
ironcadiz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
inject_into_file( | ||
'app/controllers/graphql_controller.rb', | ||
"\n\n skip_before_action :verify_authenticity_token", | ||
after: '# protect_from_forgery with: :null_session' | ||
) | ||
|
||
add_readme_section :internal_dependencies, :graphql | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
creo que esto se parece a una de las cosas que teníamos en potassium antes y que terminamos sacando porque generaba problemas de replicabilidad development/prod y hacía el debugging más oscuro, no sé si es exacto el caso, pero en general tiendo a preferir que estas condiciones sean solo pa casos muy particulares. Porque si los errores pasan de forma distinta en prod que en dev, se vuelve cacho. Aquí está lo que decía.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eso es parte del boiler plate de la gema.
Entiendo que es su forma de solucionar que solo cuando se caiga en development el endpoint de graphql te responda con el backtrace y el error.
Me hace sentido lo que dices de todas maneras, voy a revisar como se comporta sin eso