Skip to content

Commit d178626

Browse files
committed
WIP POC, kind of works
1 parent dbed72d commit d178626

8 files changed

Lines changed: 144 additions & 7 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ README.md.*
1515
/spec/dummy/tmp/
1616
/Gemfile.lock
1717
*.gemfile.lock
18-
/*.sqlite3
18+
*.sqlite3
19+
*.sqlite3-journal
20+
1921
/spec/dummy/db/development.sqlite3
2022
/spec/dummy/db/test.sqlite3
2123
/*.gem

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@
1212
GraphqlDevise::Schema.query(GraphqlDevise::Types::QueryType)
1313

1414
GraphqlDevise.load_schema
15+
16+
Devise.mailer.helper(GraphqlDevise::MailerHelper)
1517
end
1618
end

lib/graphql_devise.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,5 @@ def self.mapping_by_name(name)
5555
require 'graphql_devise/mount_method/options_validator'
5656
require 'graphql_devise/mount_method/operation_preparer'
5757
require 'graphql_devise/mount_method/operation_sanitizer'
58+
59+
require 'graphql_devise/schema_plugin'

lib/graphql_devise/rails/routes.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ def mount_graphql_devise_for(resource, options = {})
5555
end
5656

5757
GraphqlDevise.add_mapping(mapping_name, resource)
58-
59-
Devise.mailer.helper(GraphqlDevise::MailerHelper)
6058
end
6159
end
6260
end
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
module GraphqlDevise
2+
class SchemaPlugin
3+
DEFAULT_NOT_AUTHENTICATED = ->(type, field) { raise GraphqlDevise::UserError, "#{type}.#{field} requires authentication" }
4+
5+
def initialize(resource, options, query, default = true, mutation = nil, unauthenticated = DEFAULT_NOT_AUTHENTICATED)
6+
@resource = resource
7+
@options = options
8+
@query = query
9+
@mutation = mutation
10+
@unauthenticated = unauthenticated
11+
@default = default
12+
13+
load_fields
14+
end
15+
16+
def use(schema_definition)
17+
schema_definition.instrument(:field, self)
18+
end
19+
20+
def instrument(type, field)
21+
return field unless type.name == 'Query' || type.name == 'Mutation'
22+
23+
auth_value = find_auth_value(type, field)
24+
authentication_required = if auth_value.nil?
25+
@default
26+
else
27+
auth_value
28+
end
29+
30+
old_resolve_proc = field.resolve_proc
31+
new_resolve_proc = lambda do |object, arguments, context|
32+
@unauthenticated.call(type, field.name.to_sym) if authentication_required && context[:current_user].blank?
33+
34+
old_resolve_proc.call(object, arguments, context)
35+
end
36+
37+
field.redefine { resolve(new_resolve_proc) }
38+
end
39+
40+
private
41+
42+
def load_fields
43+
default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES)
44+
mapping_name = @resource.to_s.underscore.tr('/', '_').to_sym
45+
46+
# clean_options responds to all keys defined in GraphqlDevise::MountMethod::SUPPORTED_OPTIONS
47+
clean_options = GraphqlDevise::MountMethod::OptionSanitizer.new(@options).call!
48+
49+
GraphqlDevise::MountMethod::OptionsValidator.new(
50+
[
51+
GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: clean_options),
52+
GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new(
53+
options: clean_options, supported_operations: default_operations
54+
)
55+
]
56+
).validate!
57+
58+
authenticatable_type = clean_options.authenticatable_type.presence ||
59+
"Types::#{@resource}Type".safe_constantize ||
60+
GraphqlDevise::Types::AuthenticatableType
61+
62+
prepared_mutations = GraphqlDevise::MountMethod::OperationPreparer.new(
63+
mapping_name: mapping_name,
64+
custom: clean_options.operations,
65+
additional_operations: clean_options.additional_mutations,
66+
preparer: GraphqlDevise::MountMethod::OperationPreparers::MutationFieldSetter.new(authenticatable_type),
67+
selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
68+
default: GraphqlDevise::DefaultOperations::MUTATIONS, only: clean_options.only, skipped: clean_options.skip
69+
)
70+
).call
71+
72+
raise Error 'You need to define a mutation type' if prepared_mutations.any? && @mutation.blank?
73+
74+
prepared_mutations.each do |action, mutation|
75+
@mutation.field(action, mutation: mutation, authenticate: false)
76+
end
77+
78+
prepared_queries = GraphqlDevise::MountMethod::OperationPreparer.new(
79+
mapping_name: mapping_name,
80+
custom: clean_options.operations,
81+
additional_operations: clean_options.additional_queries,
82+
preparer: GraphqlDevise::MountMethod::OperationPreparers::ResolverTypeSetter.new(authenticatable_type),
83+
selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
84+
default: GraphqlDevise::DefaultOperations::QUERIES, only: clean_options.only, skipped: clean_options.skip
85+
)
86+
).call
87+
88+
prepared_queries.each do |action, resolver|
89+
@query.field(action, resolver: resolver, authenticate: false)
90+
end
91+
92+
GraphqlDevise.add_mapping(mapping_name, @resource)
93+
end
94+
95+
def find_auth_value(_, field)
96+
field.metadata[:authenticate]
97+
end
98+
end
99+
end
100+
101+
GraphQL::Field.accepts_definitions(authenticate: GraphQL::Define.assign_metadata_key(:authenticate))
102+
GraphQL::Schema::Field.accepts_definition(:authenticate)

spec/dummy/app/controllers/api/v1/graphql_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ module V1
33
class GraphqlController < ApplicationController
44
include GraphqlDevise::Concerns::SetUserByToken
55

6-
before_action :authenticate_user!
6+
before_action -> { set_user_by_token(:user) }
77

88
def graphql
9-
render json: DummySchema.execute(params[:query])
9+
render json: DummySchema.execute(params[:query], context: { current_user: current_user, controller: self })
1010
end
1111

1212
private
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
class DummySchema < GraphQL::Schema
2+
use GraphqlDevise::SchemaPlugin.new(
3+
User,
4+
{},
5+
Types::QueryType,
6+
true,
7+
Types::MutationType
8+
)
9+
210
mutation(Types::MutationType)
311
query(Types::QueryType)
412
end

spec/requests/user_controller_spec.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
RSpec.describe 'Integrations with the user controller' do
44
include_context 'with graphql query request'
55

6-
let(:user) { create(:user, :confirmed) }
6+
let(:user) { create(:user, :confirmed, password: '12345678') }
77
let(:query) do
88
<<-GRAPHQL
99
query {
@@ -33,7 +33,30 @@
3333
context 'when user is not authenticated' do
3434
it 'returns a must sign in error' do
3535
expect(json_response[:errors]).to contain_exactly(
36-
'You need to sign in or sign up before continuing.'
36+
hash_including(message: 'Query.user requires authentication', extensions: { code: 'USER_ERROR' })
37+
)
38+
end
39+
end
40+
41+
context 'when auth mutation' do
42+
let(:query) do
43+
<<-GRAPHQL
44+
mutation {
45+
userLogin(
46+
email: "#{user.email}",
47+
password: "12345678"
48+
) {
49+
authenticatable { email }
50+
credentials { accessToken client uid }
51+
}
52+
}
53+
GRAPHQL
54+
end
55+
56+
it 'works' do
57+
expect(json_response[:data][:userLogin]).to include(
58+
authenticatable: { email: user.email },
59+
credentials: hash_including(uid: user.uid)
3760
)
3861
end
3962
end

0 commit comments

Comments
 (0)