-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
425 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.