graphql_model_mapper 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -1
- data/README.md +8 -10
- data/graphql_model_mapper.gemspec +2 -0
- data/lib/graphql_model_mapper.rb +6 -1
- data/lib/graphql_model_mapper/encryption.rb +20 -0
- data/lib/graphql_model_mapper/mapper_type.rb +73 -5
- data/lib/graphql_model_mapper/mutation.rb +18 -6
- data/lib/graphql_model_mapper/query.rb +36 -69
- data/lib/graphql_model_mapper/resolve.rb +57 -17
- data/lib/graphql_model_mapper/schema.rb +35 -15
- data/lib/graphql_model_mapper/utility.rb +4 -0
- data/lib/graphql_model_mapper/version.rb +1 -1
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cca41868fb3cd1793a2d0fc46d26a09ff391a26
|
4
|
+
data.tar.gz: 133c44e736acb0fb3ae1e0a336c7f76f47016d2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f695954eb400f758c0f31a48c76270b853357cc1e31cca9887a59e4cd06c23769167a9a16732b2f42f3c6fd4ca6bdfb198bda757cb2acf2e01a6bdb8b50bd5b4
|
7
|
+
data.tar.gz: 3caf36685531ae7a340438c2ef08840860cc200e0a200843725f8b638cb56d864ef24a271d4e4d39fb997dc4b1ce39b2d48b2786efe9a2d712a86b893a83d962
|
data/Gemfile
CHANGED
@@ -6,6 +6,7 @@ gem 'activerecord', '~>3.2.22.5', :require => 'active_record'
|
|
6
6
|
gem 'activemodel', '~>3.2.22.5', :require => 'active_model'
|
7
7
|
gem 'activesupport', '~>3.2.22.5', :require => 'active_support'
|
8
8
|
gem 'graphql', '~>1.7.5'
|
9
|
+
gem 'graphql-errors', '~>0.1.0'
|
9
10
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
10
11
|
|
11
12
|
# Specify your gem's dependencies in graphql_model_mapper.gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
graphql_model_mapper (0.1.
|
4
|
+
graphql_model_mapper (0.1.1)
|
5
5
|
activemodel (>= 3.2.22.5)
|
6
6
|
activerecord (>= 3.2.22.5)
|
7
7
|
activesupport (>= 3.2.22.5)
|
8
8
|
graphql (>= 1.7.5)
|
9
|
+
graphql-errors (>= 0.1.0)
|
9
10
|
rails (>= 3.2.22.5)
|
10
11
|
|
11
12
|
GEM
|
@@ -43,6 +44,8 @@ GEM
|
|
43
44
|
concurrent-ruby (1.0.5)
|
44
45
|
erubis (2.7.0)
|
45
46
|
graphql (1.7.5)
|
47
|
+
graphql-errors (0.1.0)
|
48
|
+
graphql (>= 1.6.0, < 2)
|
46
49
|
hike (1.2.3)
|
47
50
|
i18n (0.9.1)
|
48
51
|
concurrent-ruby (~> 1.0)
|
@@ -101,6 +104,7 @@ DEPENDENCIES
|
|
101
104
|
activesupport (~> 3.2.22.5)
|
102
105
|
bundler (~> 1.16)
|
103
106
|
graphql (~> 1.7.5)
|
107
|
+
graphql-errors (~> 0.1.0)
|
104
108
|
graphql_model_mapper!
|
105
109
|
minitest (~> 5.0)
|
106
110
|
rails (~> 3.2.22.5)
|
data/README.md
CHANGED
@@ -44,9 +44,7 @@ The default input/output types generated for the model are based on the default
|
|
44
44
|
primary_keys: true,
|
45
45
|
validation_keys: false,
|
46
46
|
association_macro: nil,
|
47
|
-
source_nulls: false
|
48
|
-
type_key: :query,
|
49
|
-
type_sub_key: :input_type
|
47
|
+
source_nulls: false
|
50
48
|
},
|
51
49
|
output_type: {
|
52
50
|
required_attributes: [],
|
@@ -56,9 +54,7 @@ The default input/output types generated for the model are based on the default
|
|
56
54
|
primary_keys: true,
|
57
55
|
validation_keys: false,
|
58
56
|
association_macro: nil,
|
59
|
-
source_nulls: false
|
60
|
-
type_key: :query,
|
61
|
-
type_sub_key: :output_type
|
57
|
+
source_nulls: false
|
62
58
|
}
|
63
59
|
}
|
64
60
|
|
@@ -188,13 +184,14 @@ Some other attributes that you can set on the macro functions in addition to the
|
|
188
184
|
## graphql_query
|
189
185
|
|
190
186
|
description: # a short description of the query
|
191
|
-
scope_methods: # scope methods available to be used in the query, these
|
192
|
-
arguments: # a list of argument definitions to override the default arguments, if using your own arguments you will need to override the query resolver to act on those arguments
|
187
|
+
scope_methods: # scope methods available to be used in the query, these can be parameterized (must not be named parameters, must be accepted as string arguments and coerced in the method if needed) and must be written so that they valid in the presence of other tables which may be included in the associations
|
188
|
+
arguments: # a list of argument definitions to override the default GraphQL arguments, if using your own arguments you will need to override the query resolver to act on those arguments
|
193
189
|
|
194
|
-
Arguments should be a list of objects with the following
|
190
|
+
Arguments should be a list of objects with the following attributes (*required)
|
191
|
+
|
195
192
|
*name - displayed name of the attribute
|
196
193
|
*type - GraphQL type of the attribute
|
197
|
-
default - default value
|
194
|
+
default - default argument value
|
198
195
|
authorization - authorization level for the attribute (if GraphqlModelMapper.use_authorize = true this authorization will be compared to the authorized ability for the user on the model to which this attribute applies)
|
199
196
|
|
200
197
|
The default arguments handled by the default resolver and exposed on the query and delete mutations are:
|
@@ -209,6 +206,7 @@ The default arguments handled by the default resolver and exposed on the query a
|
|
209
206
|
{:name=>:offset, :type=>GraphQL::INT_TYPE, :default=>nil}, # specifies an offset for the start of the records returned
|
210
207
|
{:name=>:order, :type=>GraphQL::STRING_TYPE, :default=>nil, :authorization=>:manage}, # a string value that is passed to ActiveRecord query specifying the output order
|
211
208
|
{:name=>:where, :type=>GraphQL::STRING_TYPE.to_list_type, :default=>nil, :authorization=>:manage}] # a string array for use in ActiveRecord query, can be a string or a query/value array to be used by the query ["model.id =? and model.date is not nul]", "1"]
|
209
|
+
{:name=>:scopes, :type=>ModelScopeList, :default=>nil, :authorization=>:manage}] # a list of ModelScopeEnums exposed on the graphql_query/graphql_delete macro, :allowed_scopes and their optional arguments string array
|
212
210
|
|
213
211
|
## graphql_delete
|
214
212
|
description:
|
@@ -32,11 +32,13 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.required_ruby_version = '>= 2.1'
|
34
34
|
spec.add_runtime_dependency "graphql", ['>= 1.7.5']
|
35
|
+
spec.add_runtime_dependency "graphql-errors", ['>= 0.1.0']
|
35
36
|
spec.add_runtime_dependency "activesupport", ['>= 3.2.22.5']
|
36
37
|
spec.add_runtime_dependency "activemodel", ['>= 3.2.22.5']
|
37
38
|
spec.add_runtime_dependency "activerecord", ['>= 3.2.22.5']
|
38
39
|
spec.add_runtime_dependency "rails", ['>= 3.2.22.5']
|
39
40
|
spec.add_development_dependency "graphql", [">= 1.7.5"]
|
41
|
+
spec.add_development_dependency "graphql-errors", ['>= 0.1.0']
|
40
42
|
spec.add_development_dependency "bundler", "~> 1.16"
|
41
43
|
spec.add_development_dependency "rake", "~> 10.0"
|
42
44
|
spec.add_development_dependency "minitest", "~> 5.0"
|
data/lib/graphql_model_mapper.rb
CHANGED
@@ -6,6 +6,7 @@ require "graphql_model_mapper/query"
|
|
6
6
|
require "graphql_model_mapper/resolve"
|
7
7
|
require "graphql_model_mapper/schema"
|
8
8
|
require "graphql_model_mapper/utility"
|
9
|
+
require "graphql_model_mapper/encryption"
|
9
10
|
require "graphql_model_mapper/version"
|
10
11
|
require 'graphql_model_mapper/railtie' if defined?(Rails)
|
11
12
|
|
@@ -20,6 +21,9 @@ module GraphqlModelMapper
|
|
20
21
|
mattr_accessor :scan_for_polymorphic_associations
|
21
22
|
mattr_accessor :default_nodes_field
|
22
23
|
mattr_accessor :bidirectional_pagination
|
24
|
+
mattr_accessor :handle_errors
|
25
|
+
mattr_accessor :secret_token
|
26
|
+
|
23
27
|
|
24
28
|
|
25
29
|
@@type_case = :camelize
|
@@ -31,7 +35,8 @@ module GraphqlModelMapper
|
|
31
35
|
@@mutation_resolve_wrapper = nil
|
32
36
|
@@default_nodes_field = false
|
33
37
|
@@bidirectional_pagination = false
|
34
|
-
|
38
|
+
@@handle_errors = true
|
39
|
+
@@secret_token = '3eb6db5a9026c547c72708438d496d942e976b252138db7e4e0ee5edd7539457d3ed0fa02ee5e7179420ce5290462018591adaf5f42adcf855da04877827def2'
|
35
40
|
|
36
41
|
|
37
42
|
class << self
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GraphqlModelMapper
|
2
|
+
module Encryption
|
3
|
+
def self.key
|
4
|
+
Digest::SHA256.digest(ENV['GRAPHQL_SECRET_TOKEN'] || ENV['SECRET_TOKEN'] || GraphqlModelMapper.secret_token)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.aes(m,t)
|
8
|
+
(aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(m)).key = Digest::SHA256.digest(self.key)
|
9
|
+
aes.update(t) << aes.final
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.encode(text)
|
13
|
+
Base64.encode64(aes(:encrypt, text)).strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.decode(text)
|
17
|
+
aes(:decrypt, Base64.decode64(text))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -154,7 +154,7 @@ module GraphqlModelMapper
|
|
154
154
|
end
|
155
155
|
if reflection.macro == :has_many
|
156
156
|
if [:deep].include?(GraphqlModelMapper.nesting_strategy)
|
157
|
-
connection reflection.name.to_sym, -> {GraphqlModelMapper::MapperType.get_ar_object_with_params(klass.name, type_sub_key: type_sub_key)
|
157
|
+
connection reflection.name.to_sym, -> {GraphqlModelMapper::MapperType.get_connection_type(klass.name, GraphqlModelMapper::MapperType.get_ar_object_with_params(klass.name, type_sub_key: type_sub_key))}, property: reflection.name.to_sym, max_page_size: GraphqlModelMapper.max_page_size do
|
158
158
|
if GraphqlModelMapper.use_authorize
|
159
159
|
authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
|
160
160
|
model_name klass.name
|
@@ -162,7 +162,7 @@ module GraphqlModelMapper
|
|
162
162
|
end
|
163
163
|
end
|
164
164
|
else
|
165
|
-
field reflection.name.to_sym, -> {GraphqlModelMapper::MapperType.get_ar_object_with_params(klass.name, type_sub_key: type_sub_key)
|
165
|
+
field reflection.name.to_sym, -> {GraphqlModelMapper::MapperType.get_list_type(klass.name, GraphqlModelMapper::MapperType.get_ar_object_with_params(klass.name, type_sub_key: type_sub_key))}, property: reflection.name.to_sym do
|
166
166
|
if GraphqlModelMapper.use_authorize
|
167
167
|
authorized ->(ctx, model_name, access_type) { GraphqlModelMapper.authorized?(ctx, model_name, access_type.to_sym) }
|
168
168
|
model_name klass.name
|
@@ -175,7 +175,7 @@ module GraphqlModelMapper
|
|
175
175
|
# if a union type is defined in custom types use it, otherwise generate a union type from the association definition (requires a table scan)
|
176
176
|
custom_type_name = "#{name.classify}#{reflection.name.to_s.classify}Union"
|
177
177
|
if GraphqlModelMapper::CustomType.const_defined?(custom_type_name)
|
178
|
-
field reflection.name.to_sym, -> {GraphqlModelMapper::CustomType.const_get(custom_type_name)}
|
178
|
+
field reflection.name.to_sym, -> {GraphqlModelMapper::CustomType.const_get(custom_type_name)}
|
179
179
|
elsif GraphqlModelMapper.scan_for_polymorphic_associations
|
180
180
|
field reflection.name.to_sym, -> {GraphqlModelMapper::MapperType.get_polymorphic_type(reflection, name)}, property: reflection.name.to_sym
|
181
181
|
end
|
@@ -209,7 +209,7 @@ module GraphqlModelMapper
|
|
209
209
|
db_fields.reject{|s| (s.to_sym == :id) || required_attributes.include?(s)}.sort.each do |f|
|
210
210
|
custom_type_name = "#{name.classify}#{f.to_s.classify}AttributeOutput"
|
211
211
|
if GraphqlModelMapper::CustomType.const_defined?(custom_type_name)
|
212
|
-
field f.to_sym, GraphqlModelMapper::CustomType.const_get(custom_type_name)
|
212
|
+
field f.to_sym, GraphqlModelMapper::CustomType.const_get(custom_type_name)
|
213
213
|
else
|
214
214
|
if enums.include?(f.to_s)
|
215
215
|
field f.to_sym, -> {GraphqlModelMapper::MapperType.get_enum_object(name, enum_values[f.to_s], f.to_s)}, property: f.to_sym
|
@@ -235,7 +235,7 @@ module GraphqlModelMapper
|
|
235
235
|
parent_classes = has_with_deleted ? model.with_deleted.select("distinct #{parent_name}").map{|m| m.send("#{parent_name}".to_sym)} : model.select("distinct #{parent_name}").map{|m| m.send("#{parent_name}".to_sym)}
|
236
236
|
types = []
|
237
237
|
parent_classes.each do |p|
|
238
|
-
types << self.get_ar_object_with_params(p, type_sub_key:
|
238
|
+
types << self.get_ar_object_with_params(p, type_sub_key: :output_type)
|
239
239
|
end
|
240
240
|
|
241
241
|
ret_type = GraphQL::UnionType.define do
|
@@ -270,6 +270,74 @@ module GraphqlModelMapper
|
|
270
270
|
params
|
271
271
|
end
|
272
272
|
|
273
|
+
|
274
|
+
def self.get_connection_type(model_name, output_type)
|
275
|
+
connection_type_name = "#{GraphqlModelMapper.get_type_case(GraphqlModelMapper.get_type_name(model_name))}Connection"
|
276
|
+
if GraphqlModelMapper.defined_constant?(connection_type_name)
|
277
|
+
connection_type = GraphqlModelMapper.get_constant(connection_type_name)
|
278
|
+
else
|
279
|
+
connection_type = output_type.define_connection do
|
280
|
+
name connection_type_name
|
281
|
+
field :total, hash_key: :total do
|
282
|
+
type types.Int
|
283
|
+
resolve ->(obj, args, ctx) {
|
284
|
+
obj.nodes.limit(nil).count
|
285
|
+
#obj.nodes.length
|
286
|
+
}
|
287
|
+
end
|
288
|
+
end
|
289
|
+
GraphqlModelMapper.set_constant(connection_type_name, connection_type)
|
290
|
+
end
|
291
|
+
return GraphqlModelMapper.get_constant(connection_type_name)
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.get_list_type(model_name, output_type)
|
295
|
+
list_type_name = "#{GraphqlModelMapper.get_type_case(GraphqlModelMapper.get_type_name(model_name))}List"
|
296
|
+
if GraphqlModelMapper.defined_constant?(list_type_name)
|
297
|
+
list_type = GraphqlModelMapper.get_constant(list_type_name)
|
298
|
+
else
|
299
|
+
list_type = GraphQL::ObjectType.define do
|
300
|
+
name(list_type_name)
|
301
|
+
|
302
|
+
field :items, -> {output_type.to_list_type}, hash_key: :items do
|
303
|
+
argument :per_page, GraphQL::INT_TYPE
|
304
|
+
argument :page, GraphQL::INT_TYPE
|
305
|
+
resolve -> (obj,args,ctx){ GraphqlModelMapper::MapperType.resolve_list(obj,args,ctx) }
|
306
|
+
end
|
307
|
+
field :total, -> {GraphQL::INT_TYPE}, hash_key: :total do
|
308
|
+
resolve->(obj,args, ctx){
|
309
|
+
obj.count
|
310
|
+
}
|
311
|
+
end
|
312
|
+
end
|
313
|
+
GraphqlModelMapper.set_constant(list_type_name, list_type)
|
314
|
+
end
|
315
|
+
GraphqlModelMapper.get_constant(list_type_name)
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
def self.resolve_list(obj, args, ctx)
|
320
|
+
first_rec = nil
|
321
|
+
last_rec = nil
|
322
|
+
limit = GraphqlModelMapper.max_page_size.to_i
|
323
|
+
|
324
|
+
if args[:per_page]
|
325
|
+
per_page = args[:per_page].to_i
|
326
|
+
raise GraphQL::ExecutionError.new("per_page must be greater than 0") if per_page < 1
|
327
|
+
raise GraphQL::ExecutionError.new("you requested more items than the maximum page size #{limit}, please reduce your requested per_page entry") if per_page > limit
|
328
|
+
limit = [per_page,limit].min
|
329
|
+
end
|
330
|
+
if args[:page]
|
331
|
+
page = args[:page].to_i
|
332
|
+
raise GraphQL::ExecutionError.new("page must be greater than 0") if page < 1
|
333
|
+
max_page = (obj.count/limit).ceil
|
334
|
+
raise GraphQL::ExecutionError.new("you requested page #{page} which is greater than the maximum number of pages #{max_page}") if page > max_page
|
335
|
+
obj = obj.offset((page-1)*limit)
|
336
|
+
end
|
337
|
+
obj = obj.limit(limit)
|
338
|
+
obj
|
339
|
+
end
|
340
|
+
|
273
341
|
def self.graphql_default_types(
|
274
342
|
input_type: {
|
275
343
|
required_attributes: [],
|
@@ -129,19 +129,31 @@ module GraphqlModelMapper
|
|
129
129
|
allowed_scope_methods << s if (model.public_methods - model.instance_methods - Object.methods - ActiveRecord::Base.methods).include?(s)
|
130
130
|
end
|
131
131
|
if allowed_scope_methods.count > 0
|
132
|
-
|
133
|
-
if !GraphqlModelMapper.defined_constant?(
|
132
|
+
scope_enum_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(model.name)}Scope_Enum")
|
133
|
+
if !GraphqlModelMapper.defined_constant?(scope_enum_type_name)
|
134
134
|
enum_type = GraphQL::EnumType.define do
|
135
|
-
name
|
135
|
+
name scope_enum_type_name
|
136
136
|
description "scope enum for #{GraphqlModelMapper.get_type_name(model.name)}"
|
137
137
|
allowed_scope_methods.sort.each do |s|
|
138
138
|
value(s, "")
|
139
139
|
end
|
140
140
|
end
|
141
|
-
GraphqlModelMapper.set_constant
|
141
|
+
GraphqlModelMapper.set_constant scope_enum_type_name, enum_type
|
142
142
|
end
|
143
|
-
default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
|
144
|
-
|
143
|
+
#default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
|
144
|
+
|
145
|
+
scope_list_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(model.name)}Scope_List")
|
146
|
+
if !GraphqlModelMapper.defined_constant?(scope_list_type_name)
|
147
|
+
scope_list_type = GraphQL::InputObjectType.define do
|
148
|
+
name scope_list_type_name
|
149
|
+
description "scope list for #{GraphqlModelMapper.get_type_name(model.name)}"
|
150
|
+
argument :scope, !GraphqlModelMapper.get_constant(scope_enum_type_name)
|
151
|
+
argument :arguments, GraphQL::STRING_TYPE.to_list_type
|
152
|
+
end
|
153
|
+
GraphqlModelMapper.set_constant scope_list_type_name, scope_list_type
|
154
|
+
end
|
155
|
+
default_arguments << {:name=>:scopes, :type=>GraphqlModelMapper.get_constant(scope_list_type_name).to_list_type , :default=>nil, :authorization=>:manage}
|
156
|
+
end
|
145
157
|
end
|
146
158
|
default_arguments
|
147
159
|
end
|
@@ -48,19 +48,31 @@ module GraphqlModelMapper
|
|
48
48
|
allowed_scope_methods << s if (model.public_methods - model.instance_methods - Object.methods - ActiveRecord::Base.methods).include?(s)
|
49
49
|
end
|
50
50
|
if allowed_scope_methods.count > 0
|
51
|
-
|
52
|
-
if !GraphqlModelMapper.defined_constant?(
|
51
|
+
scope_enum_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(model.name)}Scope_Enum")
|
52
|
+
if !GraphqlModelMapper.defined_constant?(scope_enum_type_name)
|
53
53
|
enum_type = GraphQL::EnumType.define do
|
54
|
-
name
|
54
|
+
name scope_enum_type_name
|
55
55
|
description "scope enum for #{GraphqlModelMapper.get_type_name(model.name)}"
|
56
56
|
allowed_scope_methods.sort.each do |s|
|
57
57
|
value(s, "")
|
58
58
|
end
|
59
59
|
end
|
60
|
-
GraphqlModelMapper.set_constant
|
60
|
+
GraphqlModelMapper.set_constant scope_enum_type_name, enum_type
|
61
61
|
end
|
62
|
-
default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
|
63
|
-
|
62
|
+
#default_arguments << {:name=>:scope, :type=>GraphqlModelMapper.get_constant(typename), :default=>nil, :authorization=>:manage}
|
63
|
+
|
64
|
+
scope_list_type_name = GraphqlModelMapper.get_type_case("#{GraphqlModelMapper.get_type_name(model.name)}Scope_List")
|
65
|
+
if !GraphqlModelMapper.defined_constant?(scope_list_type_name)
|
66
|
+
scope_list_type = GraphQL::InputObjectType.define do
|
67
|
+
name scope_list_type_name
|
68
|
+
description "scope list for #{GraphqlModelMapper.get_type_name(model.name)}"
|
69
|
+
argument :scope, !GraphqlModelMapper.get_constant(scope_enum_type_name)
|
70
|
+
argument :arguments, GraphQL::STRING_TYPE.to_list_type
|
71
|
+
end
|
72
|
+
GraphqlModelMapper.set_constant scope_list_type_name, scope_list_type
|
73
|
+
end
|
74
|
+
default_arguments << {:name=>:scopes, :type=>GraphqlModelMapper.get_constant(scope_list_type_name).to_list_type , :default=>nil, :authorization=>:manage}
|
75
|
+
end
|
64
76
|
end
|
65
77
|
default_arguments
|
66
78
|
end
|
@@ -92,7 +104,6 @@ module GraphqlModelMapper
|
|
92
104
|
=end
|
93
105
|
=begin
|
94
106
|
page_info_type_name = "FlatPageInfo"
|
95
|
-
|
96
107
|
if GraphqlModelMapper.defined_constant?(page_info_type_name)
|
97
108
|
page_info_type = GraphqlModelMapper.get_constant(page_info_type_name)
|
98
109
|
else
|
@@ -108,74 +119,30 @@ module GraphqlModelMapper
|
|
108
119
|
end
|
109
120
|
=end
|
110
121
|
|
111
|
-
if [:deep, :shallow].include?(GraphqlModelMapper.nesting_strategy)
|
112
|
-
connection_type_name = "#{GraphqlModelMapper.get_type_case(GraphqlModelMapper.get_type_name(name))}ConnectionType"
|
113
|
-
if GraphqlModelMapper.defined_constant?(connection_type_name)
|
114
|
-
connection_type = GraphqlModelMapper.get_constant(connection_type_name)
|
115
|
-
else
|
116
|
-
connection_type = output_type.define_connection do
|
117
|
-
name connection_type_name
|
118
|
-
field :total, hash_key: :total do
|
119
|
-
type types.Int
|
120
|
-
resolve ->(obj, args, ctx) {
|
121
|
-
obj.nodes.count
|
122
|
-
}
|
123
|
-
end
|
124
|
-
end
|
125
|
-
GraphqlModelMapper.set_constant(connection_type_name, connection_type)
|
126
|
-
end
|
127
|
-
end
|
128
122
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
123
|
+
|
124
|
+
total_output_type_name = "#{GraphqlModelMapper.get_type_name(name)}QueryPayload"
|
125
|
+
if GraphqlModelMapper.defined_constant?(total_output_type_name)
|
126
|
+
total_output_type = GraphqlModelMapper.get_constant(total_output_type_name)
|
127
|
+
else
|
128
|
+
if [:deep, :shallow].include?(GraphqlModelMapper.nesting_strategy)
|
129
|
+
connection_type = GraphqlModelMapper::MapperType.get_connection_type(name, output_type)
|
133
130
|
total_output_type = GraphQL::ObjectType.define do
|
134
131
|
name total_output_type_name
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
obj
|
142
|
-
}
|
143
|
-
end
|
144
|
-
else
|
145
|
-
field :items, -> {output_type.to_list_type}, hash_key: :items do
|
146
|
-
argument :per_page, GraphQL::INT_TYPE
|
147
|
-
argument :page, GraphQL::INT_TYPE
|
148
|
-
resolve->(obj, args, ctx){
|
149
|
-
first_rec = nil
|
150
|
-
last_rec = nil
|
151
|
-
limit = GraphqlModelMapper.max_page_size.to_i
|
152
|
-
|
153
|
-
if args[:per_page]
|
154
|
-
per_page = args[:per_page].to_i
|
155
|
-
raise GraphQL::ExecutionError.new("per_page must be greater than 0") if per_page < 1
|
156
|
-
raise GraphQL::ExecutionError.new("you have exceeded the maximum requested page size #{limit}") if per_page > limit
|
157
|
-
limit = [per_page,limit].min
|
158
|
-
end
|
159
|
-
if args[:page]
|
160
|
-
page = args[:page].to_i
|
161
|
-
raise GraphQL::ExecutionError.new("page must be greater than 0") if page < 1
|
162
|
-
max_page = (obj.count/per_page).floor + 1
|
163
|
-
raise GraphQL::ExecutionError.new("you requested page #{page} which is greater than the max number of pages #{max_page}") if page > max_page
|
164
|
-
obj = obj.offset((page-1)*limit)
|
165
|
-
end
|
166
|
-
obj = obj.limit(limit)
|
167
|
-
obj
|
168
|
-
}
|
169
|
-
end
|
170
|
-
field :total, -> {GraphQL::INT_TYPE}, hash_key: :total do
|
171
|
-
resolve->(obj,args, ctx){
|
172
|
-
obj.limit(nil).count
|
173
|
-
}
|
132
|
+
connection :items, -> {connection_type}, max_page_size: GraphqlModelMapper.max_page_size do
|
133
|
+
resolve -> (obj, args, ctx) {
|
134
|
+
limit = GraphqlModelMapper.max_page_size
|
135
|
+
raise GraphQL::ExecutionError.new("you have exceeded the maximum requested page size #{limit}") if args[:first].to_i > limit || args[:last].to_i > limit
|
136
|
+
obj
|
137
|
+
}
|
174
138
|
end
|
175
|
-
end
|
176
139
|
end
|
177
|
-
|
140
|
+
else
|
141
|
+
total_output_type = GraphqlModelMapper::MapperType.get_list_type(name, output_type)
|
178
142
|
end
|
143
|
+
GraphqlModelMapper.set_constant(total_output_type_name, total_output_type)
|
144
|
+
end
|
145
|
+
|
179
146
|
|
180
147
|
|
181
148
|
ret_type = GraphQL::Field.define do
|
@@ -3,6 +3,7 @@ module GraphqlModelMapper
|
|
3
3
|
def self.query_resolver(obj, args, ctx, name)
|
4
4
|
#binding.pry
|
5
5
|
obj_context = obj || name.classify.constantize
|
6
|
+
model = name.classify.constantize
|
6
7
|
select_args = args[:select] || args
|
7
8
|
|
8
9
|
if !GraphqlModelMapper.authorized?(ctx, obj_context.name, :query)
|
@@ -11,16 +12,40 @@ module GraphqlModelMapper
|
|
11
12
|
classmethods = []
|
12
13
|
scope_allowed = false
|
13
14
|
with_deleted_allowed = false
|
15
|
+
|
16
|
+
if select_args[:scopes]
|
17
|
+
input_scopes = select_args[:scopes]
|
18
|
+
allowed_scopes = []
|
19
|
+
input_scopes.each do |s|
|
20
|
+
if model.public_methods.include?(s[:scope].to_sym)
|
21
|
+
allowed_scopes << {method: s[:scope], args: s[:arguments] }
|
22
|
+
else
|
23
|
+
next
|
24
|
+
end
|
25
|
+
end
|
26
|
+
errors = []
|
27
|
+
allowed_scopes.each do |a|
|
28
|
+
begin
|
29
|
+
obj_context = obj_context.send(a[:method.to_sym], *a[:args])
|
30
|
+
rescue => e
|
31
|
+
errors << "scope method: #{a[:method]} arguments: #{a[:args] || []} error: #{e.message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
if errors.length > 0
|
35
|
+
raise GraphQL::ExecutionError.new(errors.join("; "))
|
36
|
+
end
|
37
|
+
end
|
14
38
|
if select_args[:scope]
|
15
|
-
|
16
|
-
scope_allowed = classmethods.include?(select_args[:scope].to_sym)
|
39
|
+
scope_allowed = model.public_methods.include?(select_args[:scope].to_sym)
|
17
40
|
raise GraphQL::ExecutionError.new("error: invalid scope '#{select_args[:scope]}' specified, '#{select_args[:scope]}' method does not exist on '#{obj_context.class_name.classify}'") unless scope_allowed
|
18
41
|
end
|
19
42
|
if select_args[:with_deleted]
|
20
|
-
|
21
|
-
with_deleted_allowed = classmethods.include?(:with_deleted)
|
43
|
+
with_deleted_allowed = model.public_methods.include?(:with_deleted)
|
22
44
|
raise GraphQL::ExecutionError.new("error: invalid usage of 'with_deleted', 'with_deleted' method does not exist on '#{obj_context.class_name.classify}'") unless with_deleted_allowed
|
23
45
|
end
|
46
|
+
if with_deleted_allowed && select_args[:with_deleted]
|
47
|
+
obj_context = obj_context.send(:with_deleted)
|
48
|
+
end
|
24
49
|
|
25
50
|
implied_includes = self.get_implied_includes(obj_context.name.classify.constantize, ctx.ast_node)
|
26
51
|
if !implied_includes.empty?
|
@@ -30,8 +55,12 @@ module GraphqlModelMapper
|
|
30
55
|
end
|
31
56
|
end
|
32
57
|
if select_args[:id]
|
33
|
-
|
34
|
-
|
58
|
+
type_name, item_id = nil
|
59
|
+
begin
|
60
|
+
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(GraphqlModelMapper::Encryption.decode(select_args[:id]))
|
61
|
+
rescue => e
|
62
|
+
raise GraphQL::ExecutionError.new("incorrect global id: unable to resolve id: #{e.message}")
|
63
|
+
end
|
35
64
|
raise GraphQL::ExecutionError.new("incorrect global id: unable to resolve type for id:'#{select_args[:id]}'") if type_name.nil?
|
36
65
|
model_name = GraphqlModelMapper.get_constant(type_name.upcase).metadata[:model_name].to_s.classify
|
37
66
|
raise GraphQL::ExecutionError.new("incorrect global id '#{select_args[:id]}': expected global id for '#{name}', received global id for '#{model_name}'") if model_name != name
|
@@ -41,7 +70,7 @@ module GraphqlModelMapper
|
|
41
70
|
finder_array = []
|
42
71
|
errors = []
|
43
72
|
select_args[:ids].each do |id|
|
44
|
-
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
|
73
|
+
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(GraphqlModelMapper::Encryption.decode(id))
|
45
74
|
if type_name.nil?
|
46
75
|
errors << "incorrect global id: unable to resolve type for id:'#{id}'"
|
47
76
|
next
|
@@ -66,9 +95,8 @@ module GraphqlModelMapper
|
|
66
95
|
end
|
67
96
|
if select_args[:where]
|
68
97
|
obj_context = obj_context.where(select_args[:where])
|
69
|
-
|
70
|
-
|
71
|
-
obj_context = obj_context.with_deleted
|
98
|
+
else
|
99
|
+
obj_context = obj_context.where("1=1")
|
72
100
|
end
|
73
101
|
if scope_allowed
|
74
102
|
obj_context = obj_context.send(select_args[:scope].to_sym)
|
@@ -76,15 +104,21 @@ module GraphqlModelMapper
|
|
76
104
|
if select_args[:order]
|
77
105
|
obj_context = obj_context.order(select_args[:order])
|
78
106
|
end
|
107
|
+
#check for sql errors
|
108
|
+
begin
|
109
|
+
obj_context.eager_load(implied_includes).limit(0).to_a
|
110
|
+
rescue ActiveRecord::StatementInvalid => e
|
111
|
+
raise GraphQL::ExecutionError.new(e.message.sub(" AND (1=1)", "").sub(" LIMIT 0", ""))
|
112
|
+
end
|
79
113
|
if select_args[:explain]
|
80
114
|
obj_context = obj_context.limit(1)
|
81
115
|
obj_context = obj_context.eager_load(implied_includes)
|
82
|
-
raise GraphQL::ExecutionError.new(obj_context.explain.split("\n").first.sub("EXPLAIN for: ", "").sub(" LIMIT 1", !select_args[:limit].nil? && select_args[:limit].to_f > 0 ? "LIMIT #{select_args[:limit]}" : ""))
|
116
|
+
raise GraphQL::ExecutionError.new(obj_context.explain.split("\n").first.sub("EXPLAIN for: ", "").sub(" LIMIT 1", !select_args[:limit].nil? && select_args[:limit].to_f > 0 ? "LIMIT #{select_args[:limit]}" : "").sub(" AND (1=1)","").sub(" WHERE (1=1)",""))
|
83
117
|
end
|
84
118
|
#if select_args[:limit].nil?
|
85
|
-
# obj_context = obj_context.limit(
|
119
|
+
# obj_context = obj_context.limit(GraphqlModelMapper.max_page_size+1)
|
86
120
|
#end
|
87
|
-
|
121
|
+
|
88
122
|
obj_context
|
89
123
|
end
|
90
124
|
|
@@ -105,7 +139,7 @@ module GraphqlModelMapper
|
|
105
139
|
rescue => e
|
106
140
|
raise e #GraphQL::ExecutionError.new("error: delete")
|
107
141
|
end
|
108
|
-
if model.
|
142
|
+
if model.public_methods.include?(:with_deleted)
|
109
143
|
items.with_deleted
|
110
144
|
else
|
111
145
|
items
|
@@ -141,6 +175,10 @@ module GraphqlModelMapper
|
|
141
175
|
selection.name == 'nodes'
|
142
176
|
end
|
143
177
|
|
178
|
+
def self.using_items_pagination?(selection)
|
179
|
+
selection.name == 'items'
|
180
|
+
end
|
181
|
+
|
144
182
|
def self.has_reflection_with_name?(class_name, selection_name)
|
145
183
|
class_name.reflect_on_all_associations.select{|m|m.name == selection_name.to_sym}.present?
|
146
184
|
end
|
@@ -169,6 +207,11 @@ module GraphqlModelMapper
|
|
169
207
|
next
|
170
208
|
end
|
171
209
|
|
210
|
+
if using_items_pagination?(selection)
|
211
|
+
get_implied_includes(class_name, selection, dependencies)
|
212
|
+
next
|
213
|
+
end
|
214
|
+
|
172
215
|
if using_is_items_collection?(selection)
|
173
216
|
get_implied_includes(class_name, selection, dependencies)
|
174
217
|
next
|
@@ -275,10 +318,7 @@ module GraphqlModelMapper
|
|
275
318
|
end
|
276
319
|
|
277
320
|
def call(obj, args, ctx)
|
278
|
-
begin
|
279
321
|
@resolve_func.call(obj, args, ctx)
|
280
|
-
|
281
|
-
end
|
282
322
|
end
|
283
323
|
end
|
284
324
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module GraphqlModelMapper
|
2
|
-
def self.Schema(log_query_depth: false, log_query_complexity: false, use_backtrace: false, use_authorize: false, nesting_strategy: :deep, type_case: :camelize, max_page_size: 100, scan_for_polymorphic_associations: false, mutation_resolve_wrapper: nil, query_resolve_wrapper: nil, bidirectional_pagination: false, default_nodes_field: false)
|
2
|
+
def self.Schema(log_query_depth: false, log_query_complexity: false, use_backtrace: false, use_authorize: false, nesting_strategy: :deep, type_case: :camelize, max_page_size: 100, scan_for_polymorphic_associations: false, mutation_resolve_wrapper: nil, query_resolve_wrapper: nil, bidirectional_pagination: false, default_nodes_field: false, handle_errors: false, secret_token: nil)
|
3
3
|
|
4
4
|
return GraphqlModelMapper.get_constant("GraphqlModelMapperSchema".upcase) if GraphqlModelMapper.defined_constant?("GraphqlModelMapperSchema".upcase)
|
5
5
|
GraphqlModelMapper.use_authorize = use_authorize
|
@@ -9,7 +9,8 @@ module GraphqlModelMapper
|
|
9
9
|
GraphqlModelMapper.scan_for_polymorphic_associations = scan_for_polymorphic_associations
|
10
10
|
GraphqlModelMapper.default_nodes_field = default_nodes_field
|
11
11
|
GraphqlModelMapper.bidirectional_pagination = bidirectional_pagination
|
12
|
-
|
12
|
+
GraphqlModelMapper.handle_errors = handle_errors
|
13
|
+
|
13
14
|
if query_resolve_wrapper && query_resolve_wrapper < GraphqlModelMapper::Resolve::ResolveWrapper
|
14
15
|
GraphqlModelMapper.query_resolve_wrapper = query_resolve_wrapper
|
15
16
|
else
|
@@ -22,6 +23,10 @@ module GraphqlModelMapper
|
|
22
23
|
GraphqlModelMapper.mutation_resolve_wrapper = GraphqlModelMapper::Resolve::ResolveWrapper
|
23
24
|
end
|
24
25
|
|
26
|
+
if secret_token
|
27
|
+
GraphqlModelMapper.secret_token = secret_token
|
28
|
+
end
|
29
|
+
|
25
30
|
|
26
31
|
GraphQL::Relay::ConnectionType.bidirectional_pagination = GraphqlModelMapper.bidirectional_pagination
|
27
32
|
GraphQL::Relay::ConnectionType.default_nodes_field = GraphqlModelMapper.default_nodes_field
|
@@ -50,11 +55,16 @@ module GraphqlModelMapper
|
|
50
55
|
|
51
56
|
# Create UUIDs by joining the type name & ID, then base64-encoding it
|
52
57
|
id_from_object ->(object, type_definition, context) {
|
53
|
-
GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
|
58
|
+
GraphqlModelMapper::Encryption.encode(GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id))
|
54
59
|
}
|
55
60
|
|
56
61
|
object_from_id ->(id, context) {
|
57
|
-
type_name, item_id =
|
62
|
+
type_name, item_id = nil
|
63
|
+
begin
|
64
|
+
type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(GraphqlModelMapper::Encryption.decode(id))
|
65
|
+
rescue => e
|
66
|
+
raise GraphQL::ExecutionError.new("incorrect global id: unable to resolve id: #{e.message}")
|
67
|
+
end
|
58
68
|
|
59
69
|
type = GraphqlModelMapper.get_constant(type_name.upcase)
|
60
70
|
raise GraphQL::ExecutionError.new("unknown type for id: #{id}") if type.nil?
|
@@ -65,17 +75,6 @@ module GraphqlModelMapper
|
|
65
75
|
|
66
76
|
raise GraphQL::ExecutionError.new("unauthorized access for id: #{id}") if !authorized_proc.call(context, model_name, access_type)
|
67
77
|
model = model_name.to_s.classify.constantize
|
68
|
-
=begin
|
69
|
-
args = {
|
70
|
-
item_id: item_id
|
71
|
-
}
|
72
|
-
resolver = -> (obj, args, ctx) {
|
73
|
-
items = GraphqlModelMapper::Resolve.query_resolver(obj, args, ctx, nil)
|
74
|
-
}
|
75
|
-
resolve = GraphqlModelMapper::Query.get_resolver(resolver)
|
76
|
-
resolve.call(model, args, context)
|
77
|
-
=end
|
78
|
-
|
79
78
|
model.unscoped.find(item_id)
|
80
79
|
}
|
81
80
|
end
|
@@ -83,6 +82,27 @@ module GraphqlModelMapper
|
|
83
82
|
|
84
83
|
schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |query, depth| Rails.logger.info("[******GraphqlModelMapper Query Depth] #{depth}") } if log_query_depth
|
85
84
|
schema.query_analyzers << GraphQL::Analysis::QueryComplexity.new { |query, complexity| Rails.logger.info("[******GraphqlModelMapper Query Complexity] #{complexity}")} if log_query_complexity
|
85
|
+
GraphQL::Errors.configure(schema) do
|
86
|
+
rescue_from ActiveRecord::RecordNotFound do |exception|
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
rescue_from ActiveRecord::StatementInvalid do |exception|
|
91
|
+
GraphQL::ExecutionError.new(exception.message)
|
92
|
+
end
|
93
|
+
|
94
|
+
rescue_from ActiveRecord::RecordInvalid do |exception|
|
95
|
+
GraphQL::ExecutionError.new(exception.record.errors.full_messages.join("\n"))
|
96
|
+
end
|
97
|
+
|
98
|
+
rescue_from StandardError do |exception|
|
99
|
+
GraphQL::ExecutionError.new(exception.message)
|
100
|
+
end
|
101
|
+
|
102
|
+
rescue_from do |exception|
|
103
|
+
GraphQL::ExecutionError.new(exception.message)
|
104
|
+
end
|
105
|
+
end if GraphqlModelMapper.handle_errors && GraphQL.const_defined?("Errors")
|
86
106
|
|
87
107
|
GraphqlModelMapper.set_constant("GraphqlModelMapperSchema".upcase, schema)
|
88
108
|
GraphqlModelMapper.get_constant("GraphqlModelMapperSchema".upcase)
|
@@ -19,6 +19,10 @@ module GraphqlModelMapper
|
|
19
19
|
fields
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.generate_secret_token
|
23
|
+
"secret_token: '#{SecureRandom.hex(64)}'"
|
24
|
+
end
|
25
|
+
|
22
26
|
def self.schema_mutations
|
23
27
|
fields = []
|
24
28
|
GraphqlModelMapper.implementations.select{|t| t.public_methods.include?(:graphql_create)}.each { |t|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql_model_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gene Black
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.7.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: graphql-errors
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: activesupport
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +108,20 @@ dependencies:
|
|
94
108
|
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: 1.7.5
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: graphql-errors
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.1.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.1.0
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: bundler
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,6 +185,7 @@ files:
|
|
157
185
|
- graphql_model_mapper.gemspec
|
158
186
|
- lib/graphql_model_mapper.rb
|
159
187
|
- lib/graphql_model_mapper/custom_type.rb
|
188
|
+
- lib/graphql_model_mapper/encryption.rb
|
160
189
|
- lib/graphql_model_mapper/mapper_type.rb
|
161
190
|
- lib/graphql_model_mapper/mutation.rb
|
162
191
|
- lib/graphql_model_mapper/query.rb
|