From c03b82a4b7b8846c511c9063f24f15aa41871add Mon Sep 17 00:00:00 2001 From: balintkiraly Date: Sun, 7 Feb 2021 13:03:04 +0100 Subject: [PATCH 1/4] Init graphql with an example --- Gemfile | 3 ++ Gemfile.lock | 6 ++++ app/assets/config/manifest.js | 3 ++ app/controllers/graphql_controller.rb | 48 ++++++++++++++++++++++++++ app/graphql/mutations/base_mutation.rb | 8 +++++ app/graphql/queries/base_query.rb | 4 +++ app/graphql/queries/fetch_groups.rb | 9 +++++ app/graphql/schema.rb | 4 +++ app/graphql/types/base_argument.rb | 4 +++ app/graphql/types/base_enum.rb | 4 +++ app/graphql/types/base_field.rb | 5 +++ app/graphql/types/base_input_object.rb | 5 +++ app/graphql/types/base_interface.rb | 7 ++++ app/graphql/types/base_object.rb | 5 +++ app/graphql/types/base_scalar.rb | 4 +++ app/graphql/types/base_union.rb | 4 +++ app/graphql/types/group_type.rb | 6 ++++ app/graphql/types/mutation_type.rb | 10 ++++++ app/graphql/types/query_type.rb | 7 ++++ app/models/svie_post_request.rb | 1 - config/application.rb | 2 ++ config/database.yml | 4 +-- config/routes.rb | 5 +++ 23 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 app/assets/config/manifest.js create mode 100644 app/controllers/graphql_controller.rb create mode 100644 app/graphql/mutations/base_mutation.rb create mode 100644 app/graphql/queries/base_query.rb create mode 100644 app/graphql/queries/fetch_groups.rb create mode 100644 app/graphql/schema.rb create mode 100644 app/graphql/types/base_argument.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/group_type.rb create mode 100644 app/graphql/types/mutation_type.rb create mode 100644 app/graphql/types/query_type.rb diff --git a/Gemfile b/Gemfile index 0e520b30..6afac80c 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,8 @@ gem 'activity_notification', '~> 1.7' gem 'pundit', '~> 2.1' # use twitter_cldr for localization improvements (sorting strings according to hungarian) gem 'twitter_cldr', '~> 6.4' +# use graphql for graphql endpoints +gem 'graphql', '1.9.18' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console @@ -78,4 +80,5 @@ group :development do gem 'better_errors' gem 'binding_of_caller' + gem 'graphiql-rails' end diff --git a/Gemfile.lock b/Gemfile.lock index d2563d52..25de26f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -112,6 +112,10 @@ GEM ffi (1.11.1) globalid (0.4.2) activesupport (>= 4.2.0) + graphiql-rails (1.7.0) + railties + sprockets-rails + graphql (1.9.18) hashie (3.6.0) http_parser.rb (0.6.0) httparty (0.17.1) @@ -315,6 +319,8 @@ DEPENDENCIES em-http-request exception_handler (~> 0.8.0.0) factory_bot_rails + graphiql-rails + graphql (= 1.9.18) jquery-rails kaminari minitest (~> 5.10.0) diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 00000000..20b4c66a --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link graphiql/rails/application.css +//= link graphiql/rails/application.js + diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb new file mode 100644 index 00000000..e87ad732 --- /dev/null +++ b/app/controllers/graphql_controller.rb @@ -0,0 +1,48 @@ +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 = ensure_hash(params[:variables]) + query = params[:query] + operation_name = params[:operationName] + context = { + # Query context goes here, for example: + # current_user: current_user, + } + result = Schema.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 form data, JSON body, or a blank value + def ensure_hash(ambiguous_param) + case ambiguous_param + when String + if ambiguous_param.present? + ensure_hash(JSON.parse(ambiguous_param)) + else + {} + end + when Hash, ActionController::Parameters + ambiguous_param + when nil + {} + else + raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" + end + end + + def handle_error_in_development(e) + logger.error e.message + logger.error e.backtrace.join("\n") + + render json: { error: { message: e.message, backtrace: e.backtrace }, data: {} }, status: 500 + end +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb new file mode 100644 index 00000000..0749ec03 --- /dev/null +++ b/app/graphql/mutations/base_mutation.rb @@ -0,0 +1,8 @@ +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/queries/base_query.rb b/app/graphql/queries/base_query.rb new file mode 100644 index 00000000..2f2255d2 --- /dev/null +++ b/app/graphql/queries/base_query.rb @@ -0,0 +1,4 @@ +module Queries + class BaseQuery < GraphQL::Schema::Resolver + end +end diff --git a/app/graphql/queries/fetch_groups.rb b/app/graphql/queries/fetch_groups.rb new file mode 100644 index 00000000..554fe179 --- /dev/null +++ b/app/graphql/queries/fetch_groups.rb @@ -0,0 +1,9 @@ +module Queries + class FetchGroups < Queries::BaseQuery + type [Types::GroupType], null: false + + def resolve + Group.all + end + end +end \ No newline at end of file diff --git a/app/graphql/schema.rb b/app/graphql/schema.rb new file mode 100644 index 00000000..96347ff9 --- /dev/null +++ b/app/graphql/schema.rb @@ -0,0 +1,4 @@ +class Schema < GraphQL::Schema + mutation(Types::MutationType) + query(Types::QueryType) +end diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb new file mode 100644 index 00000000..c1bfdabb --- /dev/null +++ b/app/graphql/types/base_argument.rb @@ -0,0 +1,4 @@ +module Types + class BaseArgument < GraphQL::Schema::Argument + end +end diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb new file mode 100644 index 00000000..b45a845f --- /dev/null +++ b/app/graphql/types/base_enum.rb @@ -0,0 +1,4 @@ +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 00000000..7142ef7d --- /dev/null +++ b/app/graphql/types/base_field.rb @@ -0,0 +1,5 @@ +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 00000000..c97c4796 --- /dev/null +++ b/app/graphql/types/base_input_object.rb @@ -0,0 +1,5 @@ +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 00000000..f25c9650 --- /dev/null +++ b/app/graphql/types/base_interface.rb @@ -0,0 +1,7 @@ +module Types + module BaseInterface + include GraphQL::Schema::Interface + + 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 00000000..1f918414 --- /dev/null +++ b/app/graphql/types/base_object.rb @@ -0,0 +1,5 @@ +module Types + class BaseObject < GraphQL::Schema::Object + 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 00000000..c0aa38be --- /dev/null +++ b/app/graphql/types/base_scalar.rb @@ -0,0 +1,4 @@ +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 00000000..36337fc6 --- /dev/null +++ b/app/graphql/types/base_union.rb @@ -0,0 +1,4 @@ +module Types + class BaseUnion < GraphQL::Schema::Union + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb new file mode 100644 index 00000000..0c9c7793 --- /dev/null +++ b/app/graphql/types/group_type.rb @@ -0,0 +1,6 @@ +module Types + class GroupType < Types::BaseObject + field :id, ID, null: false, description: "The ID of the Group" + field :name, String, null: false + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb new file mode 100644 index 00000000..28516a59 --- /dev/null +++ b/app/graphql/types/mutation_type.rb @@ -0,0 +1,10 @@ +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/query_type.rb b/app/graphql/types/query_type.rb new file mode 100644 index 00000000..ffe7d632 --- /dev/null +++ b/app/graphql/types/query_type.rb @@ -0,0 +1,7 @@ +module Types + class QueryType < Types::BaseObject + field :groups, resolver: Queries::FetchGroups do + description 'Find all group' + end + end +end diff --git a/app/models/svie_post_request.rb b/app/models/svie_post_request.rb index 2a13c5ed..95b19b0f 100644 --- a/app/models/svie_post_request.rb +++ b/app/models/svie_post_request.rb @@ -5,7 +5,6 @@ # id :integer not null, primary key # member_type :string # user_id :integer -# # Foreign Keys # # fk_rails_... (user_id => users.id) diff --git a/config/application.rb b/config/application.rb index 1d6dbc65..a9e81a0c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -21,6 +21,8 @@ class Application < Rails::Application } } + require "sprockets/railtie" + # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths << Rails.root.join('lib') diff --git a/config/database.yml b/config/database.yml index 6528c3ff..93135d91 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,8 +9,8 @@ default: &default encoding: unicode pool: 5 host: localhost - username: pek-next - password: pek-next + username: postgres + password: password development: <<: *default diff --git a/config/routes.rb b/config/routes.rb index bb1c8cf6..4641ae1a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,10 @@ Rails.application.routes.draw do + if Rails.env.development? + mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" + end + + post '/graphql', to: 'graphql#execute' get '/logout', to: 'sessions#destroy', as: :logout get '/login', to: 'sessions#new', as: :login get '/auth/oauth/callback', to: 'sessions#create' From 685c148f4a65b23b07d9baf7b163db3fec814edc Mon Sep 17 00:00:00 2001 From: balintkiraly Date: Sun, 7 Feb 2021 15:46:13 +0100 Subject: [PATCH 2/4] Add pagination to groups fetch --- app/graphql/queries/fetch_groups.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/graphql/queries/fetch_groups.rb b/app/graphql/queries/fetch_groups.rb index 554fe179..0c7042cb 100644 --- a/app/graphql/queries/fetch_groups.rb +++ b/app/graphql/queries/fetch_groups.rb @@ -1,6 +1,6 @@ module Queries class FetchGroups < Queries::BaseQuery - type [Types::GroupType], null: false + type Types::GroupType.connection_type, null: false def resolve Group.all From 9ee389a19ec1bc56f4c58e2baac3aae797512340 Mon Sep 17 00:00:00 2001 From: balintkiraly Date: Sun, 7 Feb 2021 16:04:22 +0100 Subject: [PATCH 3/4] Add only_active flag to groups fetch --- app/graphql/queries/fetch_groups.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/graphql/queries/fetch_groups.rb b/app/graphql/queries/fetch_groups.rb index 0c7042cb..a542f208 100644 --- a/app/graphql/queries/fetch_groups.rb +++ b/app/graphql/queries/fetch_groups.rb @@ -1,9 +1,11 @@ module Queries class FetchGroups < Queries::BaseQuery + argument :only_active, Boolean, required: false type Types::GroupType.connection_type, null: false - def resolve - Group.all + def resolve(only_active: nil) + return Group.order(:name).reject(&:inactive?) if only_active + Group.order(:name) end end end \ No newline at end of file From c6c5c85d55f4a9c1c460b6791d6257633af17b25 Mon Sep 17 00:00:00 2001 From: balintkiraly Date: Sun, 7 Feb 2021 17:17:43 +0100 Subject: [PATCH 4/4] Add signIn mutation --- app/graphql/mutations/sign_in_mutation.rb | 27 +++++++++++++++++++++++ app/graphql/types/auth_payload_type.rb | 5 +++++ app/graphql/types/mutation_type.rb | 7 ++---- app/lib/authenticator.rb | 23 +++++++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 app/graphql/mutations/sign_in_mutation.rb create mode 100644 app/graphql/types/auth_payload_type.rb create mode 100644 app/lib/authenticator.rb diff --git a/app/graphql/mutations/sign_in_mutation.rb b/app/graphql/mutations/sign_in_mutation.rb new file mode 100644 index 00000000..900a4df8 --- /dev/null +++ b/app/graphql/mutations/sign_in_mutation.rb @@ -0,0 +1,27 @@ +module Mutations + class SignInMutation < Mutations::BaseMutation + argument :access_token, String, required: true + + type Types::AuthPayloadType + + def resolve(access_token) + authenticator = Authenticator.new + user_info = authenticator.authentication(access_token) + user = User.find_by(auth_sch_id: user_info[:auth_sch_id]) + + if user + user.update_last_login! + payload = { id: user.id } + token = JWT.encode payload, "S3cR3T", 'HS256' + { + token: token + } + else + # TODO: Registration + { + token: nil + } + end + end + end +end \ No newline at end of file diff --git a/app/graphql/types/auth_payload_type.rb b/app/graphql/types/auth_payload_type.rb new file mode 100644 index 00000000..10b3dd92 --- /dev/null +++ b/app/graphql/types/auth_payload_type.rb @@ -0,0 +1,5 @@ +module Types + class AuthPayloadType < Types::BaseObject + field :token, String, null: true + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 28516a59..b7b754ab 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,10 +1,7 @@ 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" + field :sign_in, resolver: Mutations::SignInMutation do + description 'Sign in with access token' end end end diff --git a/app/lib/authenticator.rb b/app/lib/authenticator.rb new file mode 100644 index 00000000..fe510108 --- /dev/null +++ b/app/lib/authenticator.rb @@ -0,0 +1,23 @@ +class Authenticator + def initialize(connection = Faraday.new) + @connection = connection + end + + def authentication(access_token) + user = fetch_user_info(access_token) + { + auth_sch_id: user['internal_id'] + } + end + + private + + def fetch_user_info(access_token) + response = @connection.get 'https://auth.sch.bme.hu/api/profile', { + access_token: access_token + } + parsed_response = JSON.parse(response.body) + raise IOError, parsed_response['error'] unless response.success? + parsed_response + end +end