Skip to content

Commit

Permalink
Merge 7206abe into 08f6058
Browse files Browse the repository at this point in the history
  • Loading branch information
00dav00 committed Feb 16, 2020
2 parents 08f6058 + 7206abe commit 8fb2074
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 72 deletions.
4 changes: 4 additions & 0 deletions lib/graphql_devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
require 'graphql_devise/error_codes'
require 'graphql_devise/user_error'
require 'graphql_devise/detailed_user_error'
require 'graphql_devise/rails/queries_preparer'
require 'graphql_devise/rails/mutations_preparer'
require 'graphql_devise/rails/operation_checker'
require 'graphql_devise/rails/operation_sanitizer'
require 'graphql_devise/concerns/controller_methods'

module GraphqlDevise
Expand Down
29 changes: 29 additions & 0 deletions lib/graphql_devise/rails/mutations_preparer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module GraphqlDevise
class MutationsPreparer
def self.call(resource:, mutations:, authenticatable_type:)
new(resource: resource, mutations: mutations, authenticatable_type: authenticatable_type).call
end

def initialize(resource:, mutations:, authenticatable_type:)
@mapping_name = resource.underscore.tr('/', '_').to_sym
@mutations = mutations
@authenticatable_type = authenticatable_type
end

def call
result = {}

@mutations.each do |action, mutation|
mapped_action = "#{@mapping_name}_#{action}".to_sym
new_mutation = Class.new(mutation)
new_mutation.graphql_name(mapped_action.to_s.camelize(:upper))
new_mutation.field(:authenticatable, @authenticatable_type, null: true)
new_mutation.instance_variable_set(:@resource_name, @mapping_name)

result[mapped_action] = new_mutation
end

result
end
end
end
39 changes: 39 additions & 0 deletions lib/graphql_devise/rails/operation_checker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module GraphqlDevise
class OperationChecker
def self.call(mutations:, queries:, custom:, only:, skipped:)
new(
mutations: mutations,
queries: queries,
custom: custom,
only: only,
skipped: skipped
).call
end

def initialize(mutations:, queries:, custom:, only:, skipped:)
@mutations = mutations
@queries = queries
@custom = custom
@only = only
@skipped = skipped
end

def call
supported_operations = @mutations.keys + @queries.keys

if [@skipped, @only].all?(&:any?)
raise GraphqlDevise::Error, "Can't specify both `skip` and `only` options when mounting the route."
end

unless @custom.keys.all? { |custom_op| supported_operations.include?(custom_op) }
raise GraphqlDevise::Error, 'One of the custom operations is not supported. Check for typos.'
end
unless @skipped.all? { |skipped_op| supported_operations.include?(skipped_op) }
raise GraphqlDevise::Error, 'Trying to skip a non supported operation. Check for typos.'
end
unless @only.all? { |only_op| supported_operations.include?(only_op) }
raise GraphqlDevise::Error, 'One of the `only` operations is not supported. Check for typos.'
end
end
end
end
34 changes: 34 additions & 0 deletions lib/graphql_devise/rails/operation_sanitizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module GraphqlDevise
class OperationSanitizer
def self.call(default:, custom:, only:, skipped:)
new(
default: default,
custom: custom,
only: only,
skipped: skipped
).call
end

def initialize(default:, custom:, only:, skipped:)
@default = default
@custom = custom
@only = only
@skipped = skipped
end

def call
result = @default
result = result.merge(@custom.slice(*operations_whitelist))
result = result.slice(*@only) if @only.present?
result = result.except(*@skipped) if @skipped.present?

result
end

private

def operations_whitelist
@default.keys
end
end
end
29 changes: 29 additions & 0 deletions lib/graphql_devise/rails/queries_preparer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module GraphqlDevise
class QueriesPreparer
def self.call(resource:, queries:, authenticatable_type:)
new(resource: resource, queries: queries, authenticatable_type: authenticatable_type).call
end

def initialize(resource:, queries:, authenticatable_type:)
@mapping_name = resource.underscore.tr('/', '_').to_sym
@queries = queries
@authenticatable_type = authenticatable_type
end

def call
result = {}

@queries.each do |action, query|
mapped_action = "#{@mapping_name}_#{action}".to_sym
new_query = Class.new(query)
new_query.graphql_name(mapped_action.to_s.camelize(:upper))
new_query.type(@authenticatable_type, null: true)
new_query.instance_variable_set(:@resource_name, @mapping_name)

result[mapped_action] = new_query
end

result
end
end
end
149 changes: 77 additions & 72 deletions lib/graphql_devise/rails/routes.rb
Original file line number Diff line number Diff line change
@@ -1,111 +1,116 @@
module ActionDispatch::Routing
class Mapper
DEVISE_OPERATIONS = [
:sessions,
:registrations,
:passwords,
:confirmations,
:omniauth_callbacks,
:unlocks,
:invitations
].freeze

def mount_graphql_devise_for(resource, opts = {})
custom_operations = opts.fetch(:operations, {})
skipped_operations = opts.fetch(:skip, [])
only_operations = opts.fetch(:only, [])
additional_mutations = opts.fetch(:additional_mutations, {})
additional_queries = opts.fetch(:additional_queries, {})
path = opts.fetch(:at, '/graphql_auth')
mapping_name = resource.underscore.tr('/', '_').to_sym
authenticatable_type = opts[:authenticatable_type].presence ||
"Types::#{resource}Type".safe_constantize ||
GraphqlDevise::Types::AuthenticatableType
param_operations = {
custom: custom_operations,
only: only_operations,
skipped: skipped_operations
}

validate_operations!(param_operations)

devise_for(
resource.pluralize.underscore.tr('/', '_').to_sym,
module: :devise,
skip: [DEVISE_OPERATIONS]
)

prepared_mutations = GraphqlDevise::MutationsPreparer.call(
resource: resource,
mutations: GraphqlDevise::OperationSanitizer.call(
default: default_mutations, **param_operations
),
authenticatable_type: authenticatable_type
)

prepared_queries = GraphqlDevise::QueriesPreparer.call(
resource: resource,
queries: GraphqlDevise::OperationSanitizer.call(
default: default_queries, **param_operations
),
authenticatable_type: authenticatable_type
)

add_mutations!(prepared_mutations, additional_mutations)
add_queries!(prepared_queries, additional_queries)

Devise.mailer.helper(GraphqlDevise::MailerHelper)

if [skipped_operations, only_operations].all?(&:any?)
raise GraphqlDevise::Error, "Can't specify both `skip` and `only` options when mounting the route."
devise_scope mapping_name do
post path, to: 'graphql_devise/graphql#auth'
get path, to: 'graphql_devise/graphql#auth'
end
end

private

default_mutations = {
def default_mutations
{
login: GraphqlDevise::Mutations::Login,
logout: GraphqlDevise::Mutations::Logout,
sign_up: GraphqlDevise::Mutations::SignUp,
update_password: GraphqlDevise::Mutations::UpdatePassword,
send_password_reset: GraphqlDevise::Mutations::SendPasswordReset,
resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation
}.freeze
default_queries = {
}
end

def default_queries
{
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
}
supported_operations = default_mutations.keys + default_queries.keys

unless skipped_operations.all? { |skipped| supported_operations.include?(skipped) }
raise GraphqlDevise::Error, 'Trying to skip a non supported operation. Check for typos.'
end
unless only_operations.all? { |only| supported_operations.include?(only) }
raise GraphqlDevise::Error, 'One of the `only` operations is not supported. Check for typos.'
end

path = opts.fetch(:at, '/graphql_auth')
mapping_name = resource.underscore.tr('/', '_').to_sym
end

devise_for(
resource.pluralize.underscore.tr('/', '_').to_sym,
module: :devise,
skip: [:sessions, :registrations, :passwords, :confirmations, :omniauth_callbacks, :unlocks, :invitations]
def validate_operations!(param_operations)
GraphqlDevise::OperationChecker.call(
mutations: default_mutations,
queries: default_queries,
**param_operations
)
end

authenticatable_type = opts[:authenticatable_type] ||
"Types::#{resource}Type".safe_constantize ||
GraphqlDevise::Types::AuthenticatableType
def add_mutations!(prepared, additional)
all_mutations = prepared.merge(additional)

used_mutations = if only_operations.present?
default_mutations.slice(*only_operations)
else
default_mutations.except(*skipped_operations)
end
used_mutations.each do |action, mutation|
used_mutation = if custom_operations[action].present?
custom_operations[action]
else
new_mutation = Class.new(mutation)
new_mutation.graphql_name("#{resource}#{action.to_s.camelize(:upper)}")
new_mutation.field(:authenticatable, authenticatable_type, null: true)

new_mutation
end
used_mutation.instance_variable_set(:@resource_name, mapping_name)

GraphqlDevise::Types::MutationType.field("#{mapping_name}_#{action}", mutation: used_mutation)
end
additional_mutations.each do |action, mutation|
all_mutations.each do |action, mutation|
GraphqlDevise::Types::MutationType.field(action, mutation: mutation)
end

if (used_mutations.present? || additional_mutations.present?) &&
if all_mutations.present? &&
(Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
end
end

used_queries = if only_operations.present?
default_queries.slice(*only_operations)
else
default_queries.except(*skipped_operations)
end
used_queries.each do |action, query|
used_query = if custom_operations[action].present?
custom_operations[action]
else
new_query = Class.new(query)
new_query.graphql_name("#{resource}#{action.to_s.camelize(:upper)}")
new_query.type(authenticatable_type, null: true)

new_query
end
used_query.instance_variable_set(:@resource_name, mapping_name)

GraphqlDevise::Types::QueryType.field("#{mapping_name}_#{action}", resolver: used_query)
end
additional_queries.each do |action, resolver|
def add_queries!(prepared, additional)
prepared.merge(additional).each do |action, resolver|
GraphqlDevise::Types::QueryType.field(action, resolver: resolver)
end

if (used_queries.blank? || additional_queries.present?) && GraphqlDevise::Types::QueryType.fields.blank?
if (prepared.blank? || additional.present?) && GraphqlDevise::Types::QueryType.fields.blank?
GraphqlDevise::Types::QueryType.field(:dummy, resolver: GraphqlDevise::Resolvers::Dummy)
end

Devise.mailer.helper(GraphqlDevise::MailerHelper)

devise_scope mapping_name do
post path, to: 'graphql_devise/graphql#auth'
get path, to: 'graphql_devise/graphql#auth'
end
end
end
end
38 changes: 38 additions & 0 deletions spec/mutations_preparer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'rails_helper'

RSpec.describe GraphqlDevise::MutationsPreparer do
describe '.call' do
subject do
described_class.call(
resource: resource,
mutations: mutations,
authenticatable_type: auth_type
)
end

let(:resource) { 'User' }
let(:class_1) { Class.new(GraphQL::Schema::Mutation) }
let(:class_2) { GraphQL::Schema::Mutation }
let(:auth_type) { GraphqlDevise::Types::AuthenticatableType }
let(:mutations) { { mutation_1: class_1, mutation_2: class_2 } }

context 'when mutations is *NOT* empty' do
it 'assign gql attibutes to mutations and changes keys using resource map' do
result = subject

expect(result.keys).to contain_exactly(:user_mutation_1, :user_mutation_2)
expect(result.values.map(&:graphql_name)).to contain_exactly(
'UserMutation1', 'UserMutation2'
)
expect(result.values.map(&:own_fields).flat_map(&:values).map(&:type).uniq.first)
.to eq(auth_type)
end
end

context 'when mutations is empty' do
let(:mutations) { {} }

it { is_expected.to be_empty }
end
end
end
Loading

0 comments on commit 8fb2074

Please sign in to comment.