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,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module Build
|
|
7
|
+
def build
|
|
8
|
+
unless defined?(ActiveRecord::Base)
|
|
9
|
+
raise ActiveAdmin::DependencyError, "ActiveAdmin::GraphQL requires ActiveRecord."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
@aa_by_model = {}
|
|
13
|
+
@object_types = {}
|
|
14
|
+
@enum_types = {}
|
|
15
|
+
@create_input_types = {}
|
|
16
|
+
@update_input_types = {}
|
|
17
|
+
@list_filter_input_types = {}
|
|
18
|
+
@find_input_types = {}
|
|
19
|
+
@aa_by_graphql_type_name = {}
|
|
20
|
+
|
|
21
|
+
active_resources.each do |aa_res|
|
|
22
|
+
model = aa_res.resource_class
|
|
23
|
+
@aa_by_model[model] = aa_res
|
|
24
|
+
@object_types[model] = build_object_type(aa_res)
|
|
25
|
+
@aa_by_graphql_type_name[graphql_type_name_for(aa_res)] = aa_res
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
wire_belongs_to_associations
|
|
29
|
+
|
|
30
|
+
active_resources.each do |aa_res|
|
|
31
|
+
model = aa_res.resource_class
|
|
32
|
+
@create_input_types[model] = build_create_input_type(aa_res)
|
|
33
|
+
@update_input_types[model] = build_update_input_type(aa_res)
|
|
34
|
+
@list_filter_input_types[model] = build_list_filter_input_type(aa_res)
|
|
35
|
+
@find_input_types[model] = build_find_input_type(aa_res)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
union_members = @object_types.values.uniq
|
|
39
|
+
registered_resource_union =
|
|
40
|
+
if union_members.any?
|
|
41
|
+
u = Class.new(::GraphQL::Schema::Union) do
|
|
42
|
+
graphql_name "ActiveAdminRegisteredResource"
|
|
43
|
+
description "Any resource object type registered for GraphQL in this namespace."
|
|
44
|
+
possible_types(*union_members)
|
|
45
|
+
end
|
|
46
|
+
attach_registered_resource_union_visibility!(u)
|
|
47
|
+
u
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
query_type = build_query_type(registered_resource_union: registered_resource_union)
|
|
51
|
+
mutation_type = build_mutation_type
|
|
52
|
+
|
|
53
|
+
model_to_type = @object_types
|
|
54
|
+
|
|
55
|
+
schema = Class.new(::GraphQL::Schema)
|
|
56
|
+
schema.query(query_type)
|
|
57
|
+
schema.mutation(mutation_type) if mutation_type
|
|
58
|
+
dataloader_plugin = @namespace.graphql_dataloader || ::GraphQL::Dataloader
|
|
59
|
+
schema.use(dataloader_plugin)
|
|
60
|
+
apply_graphql_visibility!(schema)
|
|
61
|
+
if registered_resource_union
|
|
62
|
+
schema.define_singleton_method(:resolve_type) do |abstract_type, obj, ctx|
|
|
63
|
+
if abstract_type == registered_resource_union
|
|
64
|
+
typ = model_to_type[obj.class]
|
|
65
|
+
unless typ
|
|
66
|
+
raise ::GraphQL::ExecutionError,
|
|
67
|
+
"ActiveAdminRegisteredResource could not resolve type for #{obj.class.name}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
typ
|
|
71
|
+
else
|
|
72
|
+
super(abstract_type, obj, ctx)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
configure_schema_plugins(schema)
|
|
77
|
+
hook = @namespace.graphql_configure_schema
|
|
78
|
+
hook.call(schema) if hook.respond_to?(:call)
|
|
79
|
+
schema
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module GraphParams
|
|
7
|
+
def graph_params_from_field_kwargs(aa_res, scope: nil, q: nil, order: nil, **kw)
|
|
8
|
+
h = {}
|
|
9
|
+
h[:scope] = scope if scope.present?
|
|
10
|
+
h[:q] = q if !q.nil?
|
|
11
|
+
h[:order] = order if order.present?
|
|
12
|
+
merge_belongs_to_kw!(aa_res, kw, h)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def graph_params_for_mutation(aa_res, kw)
|
|
16
|
+
merge_belongs_to_kw!(aa_res, kw, {})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def graph_params_from_input(aa_res, input)
|
|
20
|
+
h = {}
|
|
21
|
+
return h if input.nil?
|
|
22
|
+
|
|
23
|
+
blob = input.to_h.stringify_keys
|
|
24
|
+
if (btc = aa_res.belongs_to_config)
|
|
25
|
+
k = btc.to_param.to_s
|
|
26
|
+
h[k] = blob[k] if blob.key?(k) && blob[k].present?
|
|
27
|
+
end
|
|
28
|
+
h
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def assignable_slice_from_input(aa_res, input)
|
|
32
|
+
blob = input.to_h.stringify_keys
|
|
33
|
+
names = aa_res.graphql_assignable_attribute_names.map(&:to_s)
|
|
34
|
+
if (btc = aa_res.belongs_to_config)
|
|
35
|
+
names -= [btc.to_param.to_s]
|
|
36
|
+
end
|
|
37
|
+
blob.slice(*names)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list_graph_params(aa_res, filter:, scope:, q:, order:, **kw)
|
|
41
|
+
h = graph_params_from_field_kwargs(aa_res, scope: scope, q: q, order: order, **kw)
|
|
42
|
+
return h unless filter
|
|
43
|
+
|
|
44
|
+
fh = filter.to_h.stringify_keys
|
|
45
|
+
h["scope"] = fh["scope"] if fh.key?("scope")
|
|
46
|
+
h["order"] = fh["order"] if fh.key?("order")
|
|
47
|
+
h["q"] = fh["q"] if fh.key?("q")
|
|
48
|
+
if (btc = aa_res.belongs_to_config)
|
|
49
|
+
pk = btc.to_param.to_s
|
|
50
|
+
h[pk] = fh[pk] if fh.key?(pk)
|
|
51
|
+
end
|
|
52
|
+
h
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def graph_params_from_find_blob(aa_res, blob)
|
|
56
|
+
h = {}
|
|
57
|
+
if (btc = aa_res.belongs_to_config)
|
|
58
|
+
k = btc.to_param.to_s
|
|
59
|
+
h[k] = blob[k] if blob.key?(k)
|
|
60
|
+
end
|
|
61
|
+
h
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def merge_belongs_to_kw!(aa_res, kw, h)
|
|
65
|
+
if (btc = aa_res.belongs_to_config)
|
|
66
|
+
key = btc.to_param
|
|
67
|
+
val = kw[key] || kw[key.to_s] || kw[key.to_sym]
|
|
68
|
+
h[key] = val if val.present?
|
|
69
|
+
end
|
|
70
|
+
h
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationActionTypes
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def defined_actions_for(aa_res)
|
|
10
|
+
aa_res.controller.instance_methods.map(&:to_sym) & ActiveAdmin::ResourceController::ACTIVE_ADMIN_ACTIONS
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run_action_return_type(aa_res, kind)
|
|
14
|
+
cfg = aa_res.graphql_config
|
|
15
|
+
specific = case kind
|
|
16
|
+
when :batch then cfg.batch_run_action.payload_type
|
|
17
|
+
when :member then cfg.member_run_action.payload_type
|
|
18
|
+
when :collection then cfg.collection_run_action.payload_type
|
|
19
|
+
else
|
|
20
|
+
raise ArgumentError, "unknown run-action kind #{kind.inspect}"
|
|
21
|
+
end
|
|
22
|
+
ty = specific || cfg.run_action_payload_type || RunActionPayload
|
|
23
|
+
ensure_run_action_graphql_object!(aa_res, ty)
|
|
24
|
+
ty
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def member_action_return_type(aa_res, action_name)
|
|
28
|
+
per = aa_res.graphql_config.member_action_mutations[action_name.to_s]
|
|
29
|
+
ty = per&.payload_type || aa_res.graphql_config.member_run_action.payload_type ||
|
|
30
|
+
aa_res.graphql_config.run_action_payload_type || RunActionPayload
|
|
31
|
+
ensure_run_action_graphql_object!(aa_res, ty)
|
|
32
|
+
ty
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def collection_action_return_type(aa_res, action_name)
|
|
36
|
+
per = aa_res.graphql_config.collection_action_mutations[action_name.to_s]
|
|
37
|
+
ty = per&.payload_type || aa_res.graphql_config.collection_run_action.payload_type ||
|
|
38
|
+
aa_res.graphql_config.run_action_payload_type || RunActionPayload
|
|
39
|
+
ensure_run_action_graphql_object!(aa_res, ty)
|
|
40
|
+
ty
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ensure_run_action_graphql_object!(aa_res, ty)
|
|
44
|
+
unless ty.is_a?(Class) && ty < ::GraphQL::Schema::Object
|
|
45
|
+
raise ActiveAdmin::DependencyError,
|
|
46
|
+
"#{aa_res.resource_name} graphql run_action payload type must be a GraphQL::Schema::Object subclass, got #{ty.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationBatch
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def mutation_batch_field(sb, ns, aa_res)
|
|
10
|
+
plural = aa_res.resource_name.route_key.tr("-", "_")
|
|
11
|
+
fname = "#{plural}_batch_action"
|
|
12
|
+
allowed = aa_res.batch_actions.map { |a| a.sym.to_s }.freeze
|
|
13
|
+
batch_payload_t = run_action_return_type(aa_res, :batch)
|
|
14
|
+
|
|
15
|
+
proc do
|
|
16
|
+
field fname.to_sym, batch_payload_t, null: false, camelize: false,
|
|
17
|
+
visibility: {kind: :mutation_batch_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname},
|
|
18
|
+
description: "Run a batch action registered on #{aa_res.resource_name} (+batch_action+ DSL), " \
|
|
19
|
+
"same as the index batch UI (+collection_selection+ / +batch_action_inputs+)." do
|
|
20
|
+
argument :batch_action, ::GraphQL::Types::String, required: true, camelize: false
|
|
21
|
+
argument :ids, [::GraphQL::Types::ID], required: true, camelize: false
|
|
22
|
+
argument :inputs, [KeyValuePairInput], required: false, camelize: false,
|
|
23
|
+
description: "Batch action form fields as key/value pairs (passed as +batch_action_inputs+)."
|
|
24
|
+
if (btc = aa_res.belongs_to_config)
|
|
25
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
define_method(fname.to_sym) do |batch_action:, ids:, inputs: nil, **kw|
|
|
30
|
+
auth = context[:auth]
|
|
31
|
+
mdl = aa_res.resource_class
|
|
32
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
|
|
33
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ba = batch_action.to_s
|
|
37
|
+
unless allowed.include?(ba)
|
|
38
|
+
raise ::GraphQL::ExecutionError, "Unknown batch_action #{ba.inspect} for #{plural}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
proxy = ResourceQueryProxy.new(
|
|
42
|
+
aa_resource: aa_res,
|
|
43
|
+
user: context[:auth].user,
|
|
44
|
+
namespace: ns,
|
|
45
|
+
graph_params: sb.graph_params_for_mutation(aa_res, kw)
|
|
46
|
+
)
|
|
47
|
+
sb.graphql_resolve_batch_action(
|
|
48
|
+
aa_res,
|
|
49
|
+
proxy: proxy,
|
|
50
|
+
context: context,
|
|
51
|
+
batch_action: ba,
|
|
52
|
+
ids: ids,
|
|
53
|
+
inputs: inputs
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationCollection
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def mutation_collection_fields(sb, ns, aa_res)
|
|
10
|
+
[mutation_collection_aggregate_field(sb, ns, aa_res)] + aa_res.collection_actions.map do |ca|
|
|
11
|
+
mutation_single_collection_action_field(sb, ns, aa_res, ca)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def mutation_collection_aggregate_field(sb, ns, aa_res)
|
|
16
|
+
plural = aa_res.resource_name.route_key.tr("-", "_")
|
|
17
|
+
fname = "#{plural}_collection_action"
|
|
18
|
+
allowed = aa_res.collection_actions.map { |a| a.name.to_s }.freeze
|
|
19
|
+
collection_payload_t = run_action_return_type(aa_res, :collection)
|
|
20
|
+
|
|
21
|
+
proc do
|
|
22
|
+
field fname.to_sym, collection_payload_t, null: false, camelize: false,
|
|
23
|
+
visibility: {kind: :mutation_collection_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname, aggregate: true},
|
|
24
|
+
description: "Invoke a +collection_action+ on #{aa_res.resource_name} by name (same scoping as REST +index+). Prefer per-action fields +#{plural}_collection_<action>+ for distinct inputs or return types." do
|
|
25
|
+
argument :action, ::GraphQL::Types::String, required: true, camelize: false
|
|
26
|
+
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
27
|
+
description: "Extra request params as flat key/value pairs."
|
|
28
|
+
if (btc = aa_res.belongs_to_config)
|
|
29
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
define_method(fname.to_sym) do |action:, params: nil, **kw|
|
|
34
|
+
auth = context[:auth]
|
|
35
|
+
mdl = aa_res.resource_class
|
|
36
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
|
|
37
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
act = action.to_s
|
|
41
|
+
unless allowed.include?(act)
|
|
42
|
+
raise ::GraphQL::ExecutionError, "Unknown collection action #{act.inspect} for #{plural}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
proxy = ResourceQueryProxy.new(
|
|
46
|
+
aa_resource: aa_res,
|
|
47
|
+
user: context[:auth].user,
|
|
48
|
+
namespace: ns,
|
|
49
|
+
graph_params: sb.graph_params_for_mutation(aa_res, kw)
|
|
50
|
+
)
|
|
51
|
+
sb.graphql_resolve_collection_action(
|
|
52
|
+
aa_res,
|
|
53
|
+
proxy: proxy,
|
|
54
|
+
context: context,
|
|
55
|
+
action: act,
|
|
56
|
+
params: params,
|
|
57
|
+
**kw
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def mutation_single_collection_action_field(sb, ns, aa_res, collection_action_def)
|
|
64
|
+
plural = aa_res.resource_name.route_key.tr("-", "_")
|
|
65
|
+
action_name = collection_action_def.name.to_s
|
|
66
|
+
field_name = "#{plural}_collection_#{action_name.tr("-", "_")}"
|
|
67
|
+
fname = field_name.to_sym
|
|
68
|
+
per_cfg = aa_res.graphql_config.collection_action_mutations[action_name]
|
|
69
|
+
collection_payload_t = collection_action_return_type(aa_res, action_name)
|
|
70
|
+
|
|
71
|
+
proc do
|
|
72
|
+
field fname, collection_payload_t, null: false, camelize: false,
|
|
73
|
+
visibility: {
|
|
74
|
+
kind: :mutation_collection_action,
|
|
75
|
+
graphql_type_name: sb.send(:graphql_type_name_for, aa_res),
|
|
76
|
+
resource: aa_res,
|
|
77
|
+
field_name: field_name,
|
|
78
|
+
collection_action: action_name
|
|
79
|
+
},
|
|
80
|
+
description: "Invoke +collection_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +index+)." do
|
|
81
|
+
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
82
|
+
description: "Extra request params as flat key/value pairs."
|
|
83
|
+
if (btc = aa_res.belongs_to_config)
|
|
84
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
85
|
+
end
|
|
86
|
+
if per_cfg&.arguments_proc
|
|
87
|
+
instance_exec(&per_cfg.arguments_proc)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
define_method(fname) do |params: nil, **kw|
|
|
92
|
+
auth = context[:auth]
|
|
93
|
+
mdl = aa_res.resource_class
|
|
94
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
|
|
95
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
proxy = ResourceQueryProxy.new(
|
|
99
|
+
aa_resource: aa_res,
|
|
100
|
+
user: context[:auth].user,
|
|
101
|
+
namespace: ns,
|
|
102
|
+
graph_params: sb.graph_params_for_mutation(aa_res, kw)
|
|
103
|
+
)
|
|
104
|
+
sb.graphql_resolve_collection_action(
|
|
105
|
+
aa_res,
|
|
106
|
+
proxy: proxy,
|
|
107
|
+
context: context,
|
|
108
|
+
action: action_name,
|
|
109
|
+
params: params,
|
|
110
|
+
**kw
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationCreate
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def mutation_create_field(sb, ns, aa_res, model, type_c)
|
|
10
|
+
fname = "create_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
|
|
11
|
+
create_input = @create_input_types[model]
|
|
12
|
+
|
|
13
|
+
proc do
|
|
14
|
+
field fname.to_sym, type_c, null: true, camelize: false,
|
|
15
|
+
visibility: {kind: :mutation_create, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
|
|
16
|
+
description: "Create #{model.name}" do
|
|
17
|
+
argument :input, create_input, required: true, camelize: false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
define_method(fname.to_sym) do |input:, **|
|
|
21
|
+
auth = context[:auth]
|
|
22
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, model)
|
|
23
|
+
raise ::GraphQL::ExecutionError, "not authorized to create #{model.name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
proxy = ResourceQueryProxy.new(
|
|
27
|
+
aa_resource: aa_res,
|
|
28
|
+
user: auth.user,
|
|
29
|
+
namespace: ns,
|
|
30
|
+
graph_params: sb.graph_params_from_input(aa_res, input)
|
|
31
|
+
)
|
|
32
|
+
attrs = sb.assignable_slice_from_input(aa_res, input)
|
|
33
|
+
record = if (hook = aa_res.graphql_config.resolve_create_proc)
|
|
34
|
+
hook.call(
|
|
35
|
+
proxy: proxy,
|
|
36
|
+
input: input,
|
|
37
|
+
attributes: attrs,
|
|
38
|
+
context: context,
|
|
39
|
+
auth: auth,
|
|
40
|
+
aa_resource: aa_res
|
|
41
|
+
)
|
|
42
|
+
else
|
|
43
|
+
r = proxy.build_new(attrs)
|
|
44
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, r)
|
|
45
|
+
raise ::GraphQL::ExecutionError, "not authorized to create this record"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless r.save
|
|
49
|
+
raise ::GraphQL::ExecutionError, r.errors.full_messages.to_sentence
|
|
50
|
+
end
|
|
51
|
+
r
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
|
|
55
|
+
raise ::GraphQL::ExecutionError, "not authorized to create this record"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
record
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationMember
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def mutation_member_fields(sb, ns, aa_res)
|
|
10
|
+
[mutation_member_aggregate_field(sb, ns, aa_res)] + aa_res.member_actions.map do |ma|
|
|
11
|
+
mutation_single_member_action_field(sb, ns, aa_res, ma)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def mutation_member_aggregate_field(sb, ns, aa_res)
|
|
16
|
+
plural = aa_res.resource_name.route_key.tr("-", "_")
|
|
17
|
+
fname = "#{plural}_member_action"
|
|
18
|
+
allowed = aa_res.member_actions.map { |a| a.name.to_s }.freeze
|
|
19
|
+
member_payload_t = run_action_return_type(aa_res, :member)
|
|
20
|
+
|
|
21
|
+
proc do
|
|
22
|
+
field fname.to_sym, member_payload_t, null: false, camelize: false,
|
|
23
|
+
visibility: {kind: :mutation_member_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname, aggregate: true},
|
|
24
|
+
description: "Invoke a +member_action+ on #{aa_res.resource_name} by name (same scoping as REST +show+). Prefer per-action fields +#{plural}_member_<action>+ for distinct inputs or return types." do
|
|
25
|
+
argument :action, ::GraphQL::Types::String, required: true, camelize: false
|
|
26
|
+
argument :id, ::GraphQL::Types::ID, required: true, camelize: false
|
|
27
|
+
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
28
|
+
description: "Extra request params as flat key/value pairs."
|
|
29
|
+
if (btc = aa_res.belongs_to_config)
|
|
30
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
define_method(fname.to_sym) do |action:, id:, params: nil, **kw|
|
|
35
|
+
auth = context[:auth]
|
|
36
|
+
mdl = aa_res.resource_class
|
|
37
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
|
|
38
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
act = action.to_s
|
|
42
|
+
unless allowed.include?(act)
|
|
43
|
+
raise ::GraphQL::ExecutionError, "Unknown member action #{act.inspect} for #{plural}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
proxy = ResourceQueryProxy.new(
|
|
47
|
+
aa_resource: aa_res,
|
|
48
|
+
user: context[:auth].user,
|
|
49
|
+
namespace: ns,
|
|
50
|
+
graph_params: sb.graph_params_for_mutation(aa_res, kw)
|
|
51
|
+
)
|
|
52
|
+
sb.graphql_resolve_member_action(
|
|
53
|
+
aa_res,
|
|
54
|
+
proxy: proxy,
|
|
55
|
+
context: context,
|
|
56
|
+
action: act,
|
|
57
|
+
id: id,
|
|
58
|
+
params: params,
|
|
59
|
+
**kw
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def mutation_single_member_action_field(sb, ns, aa_res, member_action_def)
|
|
66
|
+
plural = aa_res.resource_name.route_key.tr("-", "_")
|
|
67
|
+
action_name = member_action_def.name.to_s
|
|
68
|
+
field_name = "#{plural}_member_#{action_name.tr("-", "_")}"
|
|
69
|
+
fname = field_name.to_sym
|
|
70
|
+
per_cfg = aa_res.graphql_config.member_action_mutations[action_name]
|
|
71
|
+
member_payload_t = member_action_return_type(aa_res, action_name)
|
|
72
|
+
|
|
73
|
+
proc do
|
|
74
|
+
field fname, member_payload_t, null: false, camelize: false,
|
|
75
|
+
visibility: {
|
|
76
|
+
kind: :mutation_member_action,
|
|
77
|
+
graphql_type_name: sb.send(:graphql_type_name_for, aa_res),
|
|
78
|
+
resource: aa_res,
|
|
79
|
+
field_name: field_name,
|
|
80
|
+
member_action: action_name
|
|
81
|
+
},
|
|
82
|
+
description: "Invoke +member_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +show+)." do
|
|
83
|
+
argument :id, ::GraphQL::Types::ID, required: true, camelize: false
|
|
84
|
+
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
85
|
+
description: "Extra request params as flat key/value pairs."
|
|
86
|
+
if (btc = aa_res.belongs_to_config)
|
|
87
|
+
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
88
|
+
end
|
|
89
|
+
if per_cfg&.arguments_proc
|
|
90
|
+
instance_exec(&per_cfg.arguments_proc)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
define_method(fname) do |id:, params: nil, **kw|
|
|
95
|
+
auth = context[:auth]
|
|
96
|
+
mdl = aa_res.resource_class
|
|
97
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
|
|
98
|
+
raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
proxy = ResourceQueryProxy.new(
|
|
102
|
+
aa_resource: aa_res,
|
|
103
|
+
user: context[:auth].user,
|
|
104
|
+
namespace: ns,
|
|
105
|
+
graph_params: sb.graph_params_for_mutation(aa_res, kw)
|
|
106
|
+
)
|
|
107
|
+
sb.graphql_resolve_member_action(
|
|
108
|
+
aa_res,
|
|
109
|
+
proxy: proxy,
|
|
110
|
+
context: context,
|
|
111
|
+
action: action_name,
|
|
112
|
+
id: id,
|
|
113
|
+
params: params,
|
|
114
|
+
**kw
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
class SchemaBuilder
|
|
6
|
+
module MutationTypeBuilder
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def build_mutation_type
|
|
10
|
+
mutations = []
|
|
11
|
+
ns = @namespace
|
|
12
|
+
sb = self
|
|
13
|
+
|
|
14
|
+
active_resources.each do |aa_res|
|
|
15
|
+
model = aa_res.resource_class
|
|
16
|
+
type_c = @object_types[model]
|
|
17
|
+
next unless type_c
|
|
18
|
+
|
|
19
|
+
actions = defined_actions_for(aa_res)
|
|
20
|
+
|
|
21
|
+
if actions.include?(:create)
|
|
22
|
+
mutations << mutation_create_field(sb, ns, aa_res, model, type_c)
|
|
23
|
+
end
|
|
24
|
+
if actions.include?(:update)
|
|
25
|
+
mutations << mutation_update_field(sb, ns, aa_res, model, type_c)
|
|
26
|
+
end
|
|
27
|
+
if actions.include?(:destroy)
|
|
28
|
+
mutations << mutation_destroy_field(sb, ns, aa_res, model)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if aa_res.batch_actions_enabled? && aa_res.batch_actions.any?
|
|
32
|
+
mutations << mutation_batch_field(sb, ns, aa_res)
|
|
33
|
+
end
|
|
34
|
+
mutations.concat(mutation_member_fields(sb, ns, aa_res)) if aa_res.member_actions.any?
|
|
35
|
+
mutations.concat(mutation_collection_fields(sb, ns, aa_res)) if aa_res.collection_actions.any?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return nil if mutations.empty?
|
|
39
|
+
|
|
40
|
+
Class.new(::GraphQL::Schema::Object) do
|
|
41
|
+
field_class ::ActiveAdmin::GraphQL::SchemaField
|
|
42
|
+
|
|
43
|
+
graphql_name "Mutation"
|
|
44
|
+
description "ActiveAdmin GraphQL mutations (#{ns.name})"
|
|
45
|
+
|
|
46
|
+
mutations.each { |m| class_eval(&m) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|