Skip to content

Commit a7de750

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

8 files changed

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