activeadmin-graphql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/CONTRIBUTING.md +27 -0
- data/LICENSE.md +21 -0
- data/README.md +49 -0
- data/activeadmin-graphql.gemspec +66 -0
- data/app/controllers/active_admin/graphql_controller.rb +168 -0
- data/docs/graphql-api.md +486 -0
- data/lib/active_admin/graphql/auth_context.rb +35 -0
- data/lib/active_admin/graphql/engine.rb +9 -0
- data/lib/active_admin/graphql/integration.rb +135 -0
- data/lib/active_admin/graphql/key_value_pair_input.rb +48 -0
- data/lib/active_admin/graphql/railtie.rb +10 -0
- data/lib/active_admin/graphql/record_source.rb +30 -0
- data/lib/active_admin/graphql/resource_config.rb +68 -0
- data/lib/active_admin/graphql/resource_definition_dsl.rb +117 -0
- data/lib/active_admin/graphql/resource_interface.rb +25 -0
- data/lib/active_admin/graphql/resource_query_proxy/controller.rb +149 -0
- data/lib/active_admin/graphql/resource_query_proxy.rb +112 -0
- data/lib/active_admin/graphql/run_action_mutation_config.rb +23 -0
- data/lib/active_admin/graphql/run_action_mutation_dsl.rb +32 -0
- data/lib/active_admin/graphql/run_action_payload.rb +27 -0
- data/lib/active_admin/graphql/schema_builder/build.rb +84 -0
- data/lib/active_admin/graphql/schema_builder/graph_params.rb +75 -0
- data/lib/active_admin/graphql/schema_builder/mutation_action_types.rb +52 -0
- data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +61 -0
- data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +118 -0
- data/lib/active_admin/graphql/schema_builder/mutation_create.rb +65 -0
- data/lib/active_admin/graphql/schema_builder/mutation_member.rb +122 -0
- data/lib/active_admin/graphql/schema_builder/mutation_type_builder.rb +52 -0
- data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +120 -0
- data/lib/active_admin/graphql/schema_builder/query_type.rb +53 -0
- data/lib/active_admin/graphql/schema_builder/query_type_collection.rb +84 -0
- data/lib/active_admin/graphql/schema_builder/query_type_member.rb +91 -0
- data/lib/active_admin/graphql/schema_builder/query_type_pages.rb +44 -0
- data/lib/active_admin/graphql/schema_builder/query_type_registered.rb +57 -0
- data/lib/active_admin/graphql/schema_builder/resolvers.rb +116 -0
- data/lib/active_admin/graphql/schema_builder/resources.rb +48 -0
- data/lib/active_admin/graphql/schema_builder/types_inputs.rb +119 -0
- data/lib/active_admin/graphql/schema_builder/types_object.rb +96 -0
- data/lib/active_admin/graphql/schema_builder/visibility.rb +58 -0
- data/lib/active_admin/graphql/schema_builder/wire.rb +36 -0
- data/lib/active_admin/graphql/schema_builder.rb +62 -0
- data/lib/active_admin/graphql/schema_field.rb +29 -0
- data/lib/active_admin/graphql/version.rb +7 -0
- data/lib/active_admin/graphql.rb +68 -0
- data/lib/active_admin/primary_key.rb +117 -0
- data/lib/activeadmin/graphql.rb +5 -0
- metadata +389 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationUpdateDestroy
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def mutation_update_field(sb, ns, aa_res, model, type_c)
|
|
10
|
+
fname = "update_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
|
|
11
|
+
find_type = @find_input_types[model]
|
|
12
|
+
update_input = @update_input_types[model]
|
|
13
|
+
|
|
14
|
+
proc do
|
|
15
|
+
field fname.to_sym, type_c, null: true, camelize: false,
|
|
16
|
+
visibility: {kind: :mutation_update, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
|
|
17
|
+
description: "Update #{model.name}" do
|
|
18
|
+
argument :where, find_type, required: true, camelize: false
|
|
19
|
+
argument :input, update_input, required: true, camelize: false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
define_method(fname.to_sym) do |where:, input:, **|
|
|
23
|
+
auth = context[:auth]
|
|
24
|
+
blob = where.to_h.stringify_keys
|
|
25
|
+
graph = sb.graph_params_from_find_blob(aa_res, blob)
|
|
26
|
+
begin
|
|
27
|
+
rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
|
|
28
|
+
rescue ArgumentError => e
|
|
29
|
+
raise ::GraphQL::ExecutionError, e.message
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
proxy = ResourceQueryProxy.new(
|
|
33
|
+
aa_resource: aa_res,
|
|
34
|
+
user: auth.user,
|
|
35
|
+
namespace: ns,
|
|
36
|
+
graph_params: graph
|
|
37
|
+
)
|
|
38
|
+
record = proxy.find_member(rid)
|
|
39
|
+
raise ::GraphQL::ExecutionError, "not found" unless record
|
|
40
|
+
|
|
41
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::UPDATE, record)
|
|
42
|
+
raise ::GraphQL::ExecutionError, "not authorized to update this record"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attrs = sb.assignable_slice_from_input(aa_res, input)
|
|
46
|
+
record = if (hook = aa_res.graphql_config.resolve_update_proc)
|
|
47
|
+
hook.call(
|
|
48
|
+
proxy: proxy,
|
|
49
|
+
input: input,
|
|
50
|
+
attributes: attrs,
|
|
51
|
+
record: record,
|
|
52
|
+
context: context,
|
|
53
|
+
auth: auth,
|
|
54
|
+
aa_resource: aa_res
|
|
55
|
+
)
|
|
56
|
+
else
|
|
57
|
+
attrs = attrs.stringify_keys.slice(*aa_res.graphql_assignable_attribute_names)
|
|
58
|
+
unless record.update(attrs)
|
|
59
|
+
raise ::GraphQL::ExecutionError, record.errors.full_messages.to_sentence
|
|
60
|
+
end
|
|
61
|
+
record
|
|
62
|
+
end
|
|
63
|
+
record
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def mutation_destroy_field(sb, ns, aa_res, model)
|
|
69
|
+
fname = "delete_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
|
|
70
|
+
find_type = @find_input_types[model]
|
|
71
|
+
|
|
72
|
+
proc do
|
|
73
|
+
field fname.to_sym, ::GraphQL::Types::Boolean, null: false, camelize: false,
|
|
74
|
+
visibility: {kind: :mutation_delete, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
|
|
75
|
+
description: "Delete #{model.name}" do
|
|
76
|
+
argument :where, find_type, required: true, camelize: false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
define_method(fname.to_sym) do |where:, **|
|
|
80
|
+
auth = context[:auth]
|
|
81
|
+
blob = where.to_h.stringify_keys
|
|
82
|
+
graph = sb.graph_params_from_find_blob(aa_res, blob)
|
|
83
|
+
begin
|
|
84
|
+
rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
|
|
85
|
+
rescue ArgumentError => e
|
|
86
|
+
raise ::GraphQL::ExecutionError, e.message
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
proxy = ResourceQueryProxy.new(
|
|
90
|
+
aa_resource: aa_res,
|
|
91
|
+
user: auth.user,
|
|
92
|
+
namespace: ns,
|
|
93
|
+
graph_params: graph
|
|
94
|
+
)
|
|
95
|
+
record = proxy.find_member(rid)
|
|
96
|
+
raise ::GraphQL::ExecutionError, "not found" unless record
|
|
97
|
+
|
|
98
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::DESTROY, record)
|
|
99
|
+
raise ::GraphQL::ExecutionError, "not authorized to destroy this record"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if (hook = aa_res.graphql_config.resolve_destroy_proc)
|
|
103
|
+
hook.call(
|
|
104
|
+
proxy: proxy,
|
|
105
|
+
record: record,
|
|
106
|
+
context: context,
|
|
107
|
+
auth: auth,
|
|
108
|
+
aa_resource: aa_res
|
|
109
|
+
)
|
|
110
|
+
else
|
|
111
|
+
record.destroy!
|
|
112
|
+
end
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module QueryType
|
|
7
|
+
include QueryTypeRegistered
|
|
8
|
+
include QueryTypeCollection
|
|
9
|
+
include QueryTypeMember
|
|
10
|
+
include QueryTypePages
|
|
11
|
+
|
|
12
|
+
def build_query_type(registered_resource_union:)
|
|
13
|
+
builder = self
|
|
14
|
+
ns = @namespace
|
|
15
|
+
aa_by_model = @aa_by_model
|
|
16
|
+
object_types = @object_types
|
|
17
|
+
aa_by_graphql_type_name = @aa_by_graphql_type_name
|
|
18
|
+
list_filter_input_types = @list_filter_input_types
|
|
19
|
+
find_input_types = @find_input_types
|
|
20
|
+
|
|
21
|
+
Class.new(::GraphQL::Schema::Object) do
|
|
22
|
+
field_class ::ActiveAdmin::GraphQL::SchemaField
|
|
23
|
+
|
|
24
|
+
graphql_name "Query"
|
|
25
|
+
description "ActiveAdmin GraphQL API (#{ns.name})"
|
|
26
|
+
|
|
27
|
+
if registered_resource_union
|
|
28
|
+
builder.add_registered_resource_query_field!(
|
|
29
|
+
self,
|
|
30
|
+
builder: builder,
|
|
31
|
+
ns: ns,
|
|
32
|
+
registered_resource_union: registered_resource_union,
|
|
33
|
+
aa_by_graphql_type_name: aa_by_graphql_type_name
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
builder.add_model_query_fields!(
|
|
38
|
+
self,
|
|
39
|
+
builder: builder,
|
|
40
|
+
ns: ns,
|
|
41
|
+
aa_by_model: aa_by_model,
|
|
42
|
+
object_types: object_types,
|
|
43
|
+
list_filter_input_types: list_filter_input_types,
|
|
44
|
+
find_input_types: find_input_types
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
builder.add_page_query_fields!(self, builder: builder, ns: ns)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module QueryTypeCollection
|
|
7
|
+
def add_model_query_fields!(query_class, builder:, ns:, aa_by_model:, object_types:,
|
|
8
|
+
list_filter_input_types:, find_input_types:)
|
|
9
|
+
aa_by_model.each do |model, aa_res|
|
|
10
|
+
builder.define_query_collection_field!(
|
|
11
|
+
query_class, model, aa_res, builder, ns, object_types, list_filter_input_types
|
|
12
|
+
)
|
|
13
|
+
builder.define_query_member_field!(
|
|
14
|
+
query_class, model, aa_res, builder, ns, object_types, find_input_types
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def define_query_collection_field!(query_class, model, aa_res, builder, ns, object_types,
|
|
20
|
+
list_filter_input_types)
|
|
21
|
+
plural_route = aa_res.resource_name.route_key.tr("-", "_")
|
|
22
|
+
type_gn = builder.send(:graphql_type_name_for, aa_res)
|
|
23
|
+
filter_type = list_filter_input_types[model]
|
|
24
|
+
type_c = object_types[model]
|
|
25
|
+
return unless type_c
|
|
26
|
+
|
|
27
|
+
query_class.class_eval do
|
|
28
|
+
field plural_route.to_sym, type_c.connection_type, null: false, connection: true, camelize: false,
|
|
29
|
+
visibility: {
|
|
30
|
+
kind: :query_collection_field,
|
|
31
|
+
graphql_type_name: type_gn,
|
|
32
|
+
field_name: plural_route,
|
|
33
|
+
resource: aa_res
|
|
34
|
+
},
|
|
35
|
+
description: "Paginated list of #{model.name} (#{aa_res.resource_name.human}); " \
|
|
36
|
+
"pass +filter+ or legacy +q+ / +scope+ / +order+ (Ransack +q+ matches the JSON index param). " \
|
|
37
|
+
"Nested resources require the parent foreign key (same as REST route params)." do
|
|
38
|
+
argument :filter, filter_type, required: false, camelize: false
|
|
39
|
+
argument :scope, ::GraphQL::Types::String, required: false, camelize: false
|
|
40
|
+
argument :q, ::GraphQL::Types::JSON, required: false, camelize: false
|
|
41
|
+
argument :order, ::GraphQL::Types::String, required: false, camelize: false
|
|
42
|
+
if (btc = aa_res.belongs_to_config)
|
|
43
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
define_method(plural_route.to_sym) do |filter: nil, scope: nil, q: nil, order: nil, **kw|
|
|
48
|
+
auth = context[:auth]
|
|
49
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
|
|
50
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
graph_params = builder.list_graph_params(
|
|
54
|
+
aa_res,
|
|
55
|
+
filter: filter,
|
|
56
|
+
scope: scope,
|
|
57
|
+
q: q,
|
|
58
|
+
order: order,
|
|
59
|
+
**kw
|
|
60
|
+
)
|
|
61
|
+
proxy = ResourceQueryProxy.new(
|
|
62
|
+
aa_resource: aa_res,
|
|
63
|
+
user: auth.user,
|
|
64
|
+
namespace: ns,
|
|
65
|
+
graph_params: graph_params
|
|
66
|
+
)
|
|
67
|
+
builder.graphql_resolve_index(
|
|
68
|
+
aa_res,
|
|
69
|
+
proxy: proxy,
|
|
70
|
+
context: context,
|
|
71
|
+
graph_params: graph_params,
|
|
72
|
+
filter: filter,
|
|
73
|
+
scope: scope,
|
|
74
|
+
q: q,
|
|
75
|
+
order: order,
|
|
76
|
+
**kw
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module QueryTypeMember
|
|
7
|
+
def define_query_member_field!(query_class, model, aa_res, builder, ns, object_types, find_input_types)
|
|
8
|
+
singular = aa_res.resource_name.route_key.singularize.camelize(:lower)
|
|
9
|
+
type_gn = builder.send(:graphql_type_name_for, aa_res)
|
|
10
|
+
find_type = find_input_types[model]
|
|
11
|
+
type_c = object_types[model]
|
|
12
|
+
return unless type_c
|
|
13
|
+
|
|
14
|
+
query_class.class_eval do
|
|
15
|
+
field singular.to_sym, type_c, null: true, camelize: false,
|
|
16
|
+
visibility: {
|
|
17
|
+
kind: :query_member_field,
|
|
18
|
+
graphql_type_name: type_gn,
|
|
19
|
+
field_name: singular,
|
|
20
|
+
resource: aa_res
|
|
21
|
+
},
|
|
22
|
+
description: "Find #{model.name} by id (same scoping chain as REST +show+). Use +where+ or legacy +id+." do
|
|
23
|
+
argument :where, find_type, required: false, camelize: false
|
|
24
|
+
if ActiveAdmin::PrimaryKey.composite?(model)
|
|
25
|
+
argument :id, ::GraphQL::Types::ID, required: false, camelize: false,
|
|
26
|
+
description: "JSON object string with all primary keys, or use per-key arguments"
|
|
27
|
+
else
|
|
28
|
+
argument :id, ::GraphQL::Types::ID, required: false, camelize: false
|
|
29
|
+
end
|
|
30
|
+
if ActiveAdmin::PrimaryKey.composite?(model)
|
|
31
|
+
ActiveAdmin::PrimaryKey.ordered_columns(model).each do |col|
|
|
32
|
+
coldef = model.columns_hash[col]
|
|
33
|
+
next unless coldef
|
|
34
|
+
|
|
35
|
+
gql_t = builder.send(:graphql_scalar_for_column, aa_res, model, coldef)
|
|
36
|
+
argument col.to_sym, gql_t, required: false, camelize: false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
if (btc = aa_res.belongs_to_config)
|
|
40
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: false, camelize: false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
define_method(singular.to_sym) do |where: nil, id: nil, **kw|
|
|
45
|
+
auth = context[:auth]
|
|
46
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
|
|
47
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
if where
|
|
52
|
+
blob = where.to_h.stringify_keys
|
|
53
|
+
graph = builder.graph_params_from_find_blob(aa_res, blob)
|
|
54
|
+
rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
|
|
55
|
+
else
|
|
56
|
+
graph = builder.graph_params_for_mutation(aa_res, kw)
|
|
57
|
+
rid = ActiveAdmin::PrimaryKey.field_kw_to_param_hash(model, id: id, **kw)
|
|
58
|
+
end
|
|
59
|
+
rescue ArgumentError => e
|
|
60
|
+
raise ::GraphQL::ExecutionError, e.message
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
proxy = ResourceQueryProxy.new(
|
|
64
|
+
aa_resource: aa_res,
|
|
65
|
+
user: auth.user,
|
|
66
|
+
namespace: ns,
|
|
67
|
+
graph_params: graph
|
|
68
|
+
)
|
|
69
|
+
record = builder.graphql_resolve_show(
|
|
70
|
+
aa_res,
|
|
71
|
+
proxy: proxy,
|
|
72
|
+
context: context,
|
|
73
|
+
id: rid,
|
|
74
|
+
graph_params: graph,
|
|
75
|
+
where: where,
|
|
76
|
+
id_argument: id
|
|
77
|
+
)
|
|
78
|
+
return nil unless record
|
|
79
|
+
|
|
80
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
|
|
81
|
+
raise ::GraphQL::ExecutionError, "not authorized to read this record"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
record
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module QueryTypePages
|
|
7
|
+
def add_page_query_fields!(query_class, builder:, ns:)
|
|
8
|
+
ns.resources.each do |page|
|
|
9
|
+
next unless page.is_a?(ActiveAdmin::Page)
|
|
10
|
+
|
|
11
|
+
page.graphql_fields.each do |spec|
|
|
12
|
+
fname = spec[:name].to_sym
|
|
13
|
+
gql_t = spec[:type]
|
|
14
|
+
null = spec[:null]
|
|
15
|
+
desc = spec[:description]
|
|
16
|
+
resolver = spec[:resolver]
|
|
17
|
+
|
|
18
|
+
if gql_t.nil?
|
|
19
|
+
raise ArgumentError,
|
|
20
|
+
"graphql_field :#{spec[:name]} on page #{page.name.inspect} requires a GraphQL type"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
kwargs = {null: null, camelize: false, visibility: {kind: :query_page_field, field_name: fname.to_s, page: page}}
|
|
24
|
+
kwargs[:description] = desc if desc
|
|
25
|
+
query_class.class_eval do
|
|
26
|
+
field fname, gql_t, **kwargs
|
|
27
|
+
|
|
28
|
+
define_method(fname) do
|
|
29
|
+
auth = context[:auth]
|
|
30
|
+
unless auth.authorized?(page, ActiveAdmin::Authorization::READ, page)
|
|
31
|
+
raise ::GraphQL::ExecutionError, "not authorized for page field #{spec[:name]}"
|
|
32
|
+
end
|
|
33
|
+
raise ::GraphQL::ExecutionError, "graphql_field :#{spec[:name]} needs a resolver block" unless resolver
|
|
34
|
+
|
|
35
|
+
instance_exec(auth.user, context, &resolver)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module QueryTypeRegistered
|
|
7
|
+
def add_registered_resource_query_field!(query_class, builder:, ns:, registered_resource_union:,
|
|
8
|
+
aa_by_graphql_type_name:)
|
|
9
|
+
query_class.class_eval do
|
|
10
|
+
field :registered_resource, registered_resource_union, null: true, camelize: false,
|
|
11
|
+
visibility: {kind: :query_registered_resource},
|
|
12
|
+
description: "Load any registered resource by GraphQL type name (+type_name+), " \
|
|
13
|
+
"mirroring singular resource queries. Use +path+ for nested belongs_to route params." do
|
|
14
|
+
argument :type_name, ::GraphQL::Types::String, required: true, camelize: false
|
|
15
|
+
argument :id, ::GraphQL::Types::ID, required: true, camelize: false
|
|
16
|
+
argument :path, [KeyValuePairInput], required: false, camelize: false,
|
|
17
|
+
description: "Parent route params as flat key/value pairs (same keys as nested REST segments)."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
define_method(:registered_resource) do |type_name:, id:, path: nil|
|
|
21
|
+
aa_res = aa_by_graphql_type_name[type_name.to_s]
|
|
22
|
+
raise ::GraphQL::ExecutionError, "unknown resource type_name #{type_name.inspect}" unless aa_res
|
|
23
|
+
|
|
24
|
+
model = aa_res.resource_class
|
|
25
|
+
auth = context[:auth]
|
|
26
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
|
|
27
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
graph_params = KeyValuePairs.to_hash(path)
|
|
31
|
+
proxy = ResourceQueryProxy.new(
|
|
32
|
+
aa_resource: aa_res,
|
|
33
|
+
user: auth.user,
|
|
34
|
+
namespace: ns,
|
|
35
|
+
graph_params: graph_params
|
|
36
|
+
)
|
|
37
|
+
record = builder.graphql_resolve_show(
|
|
38
|
+
aa_res,
|
|
39
|
+
proxy: proxy,
|
|
40
|
+
context: context,
|
|
41
|
+
id: id,
|
|
42
|
+
graph_params: graph_params
|
|
43
|
+
)
|
|
44
|
+
return nil unless record
|
|
45
|
+
|
|
46
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
|
|
47
|
+
raise ::GraphQL::ExecutionError, "not authorized to read this record"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
record
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module Resolvers
|
|
7
|
+
def mutation_extra_keyword_params(aa_res, kw)
|
|
8
|
+
skip =
|
|
9
|
+
if (btc = aa_res.belongs_to_config)
|
|
10
|
+
[btc.to_param.to_s]
|
|
11
|
+
else
|
|
12
|
+
[]
|
|
13
|
+
end
|
|
14
|
+
kw.each_with_object({}) do |(key, val), h|
|
|
15
|
+
ks = key.to_s
|
|
16
|
+
next if skip.include?(ks)
|
|
17
|
+
|
|
18
|
+
h[ks] = val
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def graphql_resolve_index(aa_res, proxy:, context:, graph_params:, **field_kwargs)
|
|
23
|
+
proc_ = aa_res.graphql_config.resolve_index_proc
|
|
24
|
+
base_kwargs = {graph_params: graph_params}.merge(field_kwargs)
|
|
25
|
+
if proc_
|
|
26
|
+
proc_.call(proxy: proxy, context: context, aa_resource: aa_res, auth: context[:auth], **base_kwargs)
|
|
27
|
+
else
|
|
28
|
+
proxy.relation_for_index
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def graphql_resolve_show(aa_res, proxy:, context:, id:, graph_params:, **field_kwargs)
|
|
33
|
+
proc_ = aa_res.graphql_config.resolve_show_proc
|
|
34
|
+
base_kwargs = {id: id, graph_params: graph_params}.merge(field_kwargs)
|
|
35
|
+
if proc_
|
|
36
|
+
proc_.call(proxy: proxy, context: context, aa_resource: aa_res, auth: context[:auth], **base_kwargs)
|
|
37
|
+
else
|
|
38
|
+
proxy.find_member(id)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def graphql_resolve_batch_action(aa_res, proxy:, context:, batch_action:, ids:, inputs:)
|
|
43
|
+
inputs_h = coerce_action_param_map(inputs)
|
|
44
|
+
if (p = aa_res.graphql_config.batch_run_action.resolve_proc)
|
|
45
|
+
p.call(
|
|
46
|
+
proxy: proxy,
|
|
47
|
+
context: context,
|
|
48
|
+
aa_resource: aa_res,
|
|
49
|
+
auth: context[:auth],
|
|
50
|
+
batch_action: batch_action,
|
|
51
|
+
ids: ids,
|
|
52
|
+
inputs: inputs_h
|
|
53
|
+
)
|
|
54
|
+
else
|
|
55
|
+
proxy.run_batch_action(batch_action, ids, inputs: inputs_h)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def graphql_resolve_member_action(aa_res, proxy:, context:, action:, id:, params: nil, **kw)
|
|
60
|
+
extras = mutation_extra_keyword_params(aa_res, kw)
|
|
61
|
+
params_h = coerce_action_param_map(params).merge(extras.transform_keys(&:to_s))
|
|
62
|
+
per = aa_res.graphql_config.member_action_mutations[action.to_s]
|
|
63
|
+
resolve = per&.resolve_proc || aa_res.graphql_config.member_run_action.resolve_proc
|
|
64
|
+
if resolve
|
|
65
|
+
resolve.call(
|
|
66
|
+
proxy: proxy,
|
|
67
|
+
context: context,
|
|
68
|
+
aa_resource: aa_res,
|
|
69
|
+
auth: context[:auth],
|
|
70
|
+
action: action,
|
|
71
|
+
id: id,
|
|
72
|
+
params: params_h,
|
|
73
|
+
**extras
|
|
74
|
+
)
|
|
75
|
+
else
|
|
76
|
+
proxy.run_member_action(action, id, extra_params: params_h)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def graphql_resolve_collection_action(aa_res, proxy:, context:, action:, params: nil, **kw)
|
|
81
|
+
extras = mutation_extra_keyword_params(aa_res, kw)
|
|
82
|
+
params_h = coerce_action_param_map(params).merge(extras.transform_keys(&:to_s))
|
|
83
|
+
per = aa_res.graphql_config.collection_action_mutations[action.to_s]
|
|
84
|
+
resolve = per&.resolve_proc || aa_res.graphql_config.collection_run_action.resolve_proc
|
|
85
|
+
if resolve
|
|
86
|
+
resolve.call(
|
|
87
|
+
proxy: proxy,
|
|
88
|
+
context: context,
|
|
89
|
+
aa_resource: aa_res,
|
|
90
|
+
auth: context[:auth],
|
|
91
|
+
action: action,
|
|
92
|
+
params: params_h,
|
|
93
|
+
**extras
|
|
94
|
+
)
|
|
95
|
+
else
|
|
96
|
+
proxy.run_collection_action(action, extra_params: params_h)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def coerce_action_param_map(value)
|
|
101
|
+
return {} if value.nil?
|
|
102
|
+
return KeyValuePairs.to_hash(value) if value.is_a?(Array)
|
|
103
|
+
|
|
104
|
+
h = value.respond_to?(:to_unsafe_h) ? value.to_unsafe_h : value.to_h
|
|
105
|
+
h.stringify_keys
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
public :graphql_resolve_index,
|
|
109
|
+
:graphql_resolve_show,
|
|
110
|
+
:graphql_resolve_batch_action,
|
|
111
|
+
:graphql_resolve_member_action,
|
|
112
|
+
:graphql_resolve_collection_action
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module Resources
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def namespace_resources
|
|
10
|
+
@namespace.resources.select { |r| r.is_a?(ActiveAdmin::Resource) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def active_resources
|
|
14
|
+
namespace_resources.select do |r|
|
|
15
|
+
next false if r.graphql_config.disabled?
|
|
16
|
+
next false unless r.resource_class < ActiveRecord::Base
|
|
17
|
+
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def graphql_type_name_for(aa_res)
|
|
23
|
+
aa_res.graphql_config.graphql_type_name.presence ||
|
|
24
|
+
aa_res.resource_class.name.delete_prefix("::").gsub("::", "__")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def attributes_for(aa_res)
|
|
28
|
+
aa_res.attributes_for_graphql
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def configure_schema_plugins(schema)
|
|
32
|
+
if (c = @namespace.graphql_max_complexity)
|
|
33
|
+
schema.max_complexity(c)
|
|
34
|
+
end
|
|
35
|
+
if (d = @namespace.graphql_max_depth)
|
|
36
|
+
schema.max_depth(d)
|
|
37
|
+
end
|
|
38
|
+
if (ps = @namespace.graphql_default_page_size)
|
|
39
|
+
schema.default_page_size(ps)
|
|
40
|
+
end
|
|
41
|
+
if (mps = @namespace.graphql_default_max_page_size)
|
|
42
|
+
schema.default_max_page_size(mps)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|