Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Will do the following:
- Add `devise` modules to `Admin` model
- Other changes that you can find [here](https://devise-token-auth.gitbook.io/devise-token-auth/config)
- Add the route to `config/routes.rb`
- `mount_graphql_devise_for 'Admin', at: 'api/auth'
- `mount_graphql_devise_for 'Admin', at: 'api/auth'`

`Admin` could be any model name you are going to be using for authentication,
and `api/auth` could be any mount path you would like to use for auth.
Expand All @@ -66,7 +66,15 @@ Rails.application.routes.draw do
operations: {
login: Mutations::Login
},
skip: [:sign_up]
skip: [:sign_up],
additional_mutations: {
# generates mutation { adminUserSignUp }
admin_user_sign_up: Mutations::AdminUserSignUp
},
additional_queries: {
# generates query { publicUserByUuid }
public_user_by_uuid: Resolvers::UserByUuid
}
)
end
```
Expand All @@ -90,6 +98,28 @@ will use it. But, you can override this type with this option like in the exampl
symbols and should belong to the list of available operations in the gem.
1. `only`: An array of the operations that should be available in the authentication schema. The `skip` and `only` options are
mutually exclusive, an error will be raised if you pass both to the mount method.
1. `additional_mutations`: Here you can add as many mutations as you
need, for those features that don't fully match the provided default mutations and queries.
You need to provide a hash to this option, and
each key will be the name of the mutation on the schema. Also, the value provided must be a valid mutation.
This is similar to what you can accomplish with
[devise_scope](https://www.rubydoc.info/github/heartcombo/devise/master/ActionDispatch/Routing/Mapper%3Adevise_for).
1. `additional_queries`: Here you can add as many queries as you need,
for those features that don't fully match the provided default mutations and queries.
You need to provide a hash to this option, and
each key will be the name of the query on the schema. Also, the value provided must be a valid Resolver.
This is also similar to what you can accomplish with
[devise_scope](https://www.rubydoc.info/github/heartcombo/devise/master/ActionDispatch/Routing/Mapper%3Adevise_for).

Additional mutations and queries will be added to the schema regardless
of other options you might have specified like `skip` or `only`.
Additional queries and mutations is usually a good place for other
operations on your schema that require no authentication (like sign_up).
Also by adding them through the mount method, your mutations and
resolvers can inherit from our [base mutation](https://github.com/graphql-devise/graphql_devise/blob/master/app/graphql/graphql_devise/mutations/base.rb)
or [base resover](https://github.com/graphql-devise/graphql_devise/blob/master/app/graphql/graphql_devise/resolvers/base.rb)
respectively, to take advantage of some of the methods provided by devise
just like with `devise_scope`

#### Available Operations
The following is a list of the symbols you can provide to the `operations`, `skip` and `only` options of the mount method:
Expand Down
6 changes: 5 additions & 1 deletion app/graphql/graphql_devise/mutations/sign_up.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class SignUp < Base
argument :confirm_success_url, String, required: false

def resolve(confirm_success_url: nil, **attrs)
resource = resource_class.new(provider: provider, **attrs)
resource = build_resource(attrs.merge(provider: provider))
raise_user_error(I18n.t('graphql_devise.resource_build_failed')) if resource.blank?

redirect_url = confirm_success_url || DeviseTokenAuth.default_confirm_success_url
Expand Down Expand Up @@ -45,6 +45,10 @@ def resolve(confirm_success_url: nil, **attrs)

private

def build_resource(attrs)
resource_class.new(attrs)
end

def provider
:email
end
Expand Down
18 changes: 13 additions & 5 deletions lib/graphql_devise/rails/routes.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
module ActionDispatch::Routing
class Mapper
def mount_graphql_devise_for(resource, opts = {})
custom_operations = opts[:operations] || {}
skipped_operations = opts.fetch(:skip, [])
only_operations = opts.fetch(:only, [])
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, {})

if [skipped_operations, only_operations].all?(&:any?)
raise GraphqlDevise::Error, "Can't specify both `skip` and `only` options when mounting the route."
Expand Down Expand Up @@ -62,8 +64,11 @@ def mount_graphql_devise_for(resource, opts = {})

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

if used_mutations.present? &&
if (used_mutations.present? || additional_mutations.present?) &&
(Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
end
Expand All @@ -87,8 +92,11 @@ def mount_graphql_devise_for(resource, opts = {})

GraphqlDevise::Types::QueryType.field("#{mapping_name}_#{action}", resolver: used_query)
end
additional_queries.each do |action, resolver|
GraphqlDevise::Types::QueryType.field(action, resolver: resolver)
end

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

Expand Down
23 changes: 23 additions & 0 deletions spec/dummy/app/graphql/mutations/register_confirmed_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Mutations
class RegisterConfirmedUser < GraphqlDevise::Mutations::Base
argument :email, String, required: true
argument :name, String, required: true
argument :password, String, required: true
argument :password_confirmation, String, required: true

field :user, Types::UserType, null: true

def resolve(**attrs)
user = User.new(attrs.merge(confirmed_at: Time.zone.now))

if user.save
{ user: user }
else
raise_user_error_list(
'Custom registration failed',
errors: user.errors.full_messages
)
end
end
end
end
11 changes: 11 additions & 0 deletions spec/dummy/app/graphql/resolvers/public_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Resolvers
class PublicUser < GraphQL::Schema::Resolver
type Types::UserType, null: false

argument :id, Int, required: true

def resolve(id:)
User.find(id)
end
end
end
4 changes: 4 additions & 0 deletions spec/dummy/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
mount_graphql_devise_for 'User', at: '/api/v1/graphql_auth', operations: {
login: Mutations::Login,
sign_up: Mutations::SignUp
}, additional_mutations: {
register_confirmed_user: Mutations::RegisterConfirmedUser
}, additional_queries: {
public_user: Resolvers::PublicUser
}

mount_graphql_devise_for(
Expand Down
65 changes: 65 additions & 0 deletions spec/requests/mutations/additional_mutations_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'rails_helper'

RSpec.describe 'Additional Mutations' do
include_context 'with graphql query request'

let(:name) { Faker::Name.name }
let(:password) { Faker::Internet.password }
let(:password_confirmation) { password }
let(:email) { Faker::Internet.email }
let(:redirect) { Faker::Internet.url }

context 'when using the user model' do
let(:query) do
<<-GRAPHQL
mutation {
registerConfirmedUser(
email: "#{email}",
name: "#{name}",
password: "#{password}",
passwordConfirmation: "#{password_confirmation}"
) {
user {
email
name
}
}
}
GRAPHQL
end

context 'when params are correct' do
it 'creates a new resource that is already confirmed' do
expect { post_request }.to(
change(User, :count).by(1)
.and(not_change(ActionMailer::Base.deliveries, :count))
)

user = User.last

expect(user).to be_confirmed
expect(json_response[:data][:registerConfirmedUser]).to include(
user: {
email: email,
name: name
}
)
end
end

context 'when params are incorrect' do
let(:password_confirmation) { 'not the same' }

it 'returns descriptive errors' do
expect { post_request }.to not_change(User, :count)

expect(json_response[:errors]).to contain_exactly(
hash_including(
message: 'Custom registration failed',
extensions: { code: 'USER_ERROR', detailed_errors: ["Password confirmation doesn't match Password"] }
)
)
end
end
end
end
31 changes: 31 additions & 0 deletions spec/requests/mutations/additional_queries_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'rails_helper'

RSpec.describe 'Additional Queries' do
include_context 'with graphql query request'

let(:public_user) { create(:user, :confirmed) }

context 'when using the user model' do
let(:query) do
<<-GRAPHQL
query {
publicUser(
id: #{public_user.id}
) {
email
name
}
}
GRAPHQL
end

before { post_request }

it 'fetches a user by ID' do
expect(json_response[:data][:publicUser]).to include(
email: public_user.email,
name: public_user.name
)
end
end
end