groovestack-base 0.1.5
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/.rspec +1 -0
- data/.rubocop.yml +44 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +11 -0
- data/config/initializers/graphql.rb +26 -0
- data/config.ru +12 -0
- data/groovestack-base.gemspec +41 -0
- data/lib/groovestack/base/active_record.rb +19 -0
- data/lib/groovestack/base/graphql/base_input_object.rb +11 -0
- data/lib/groovestack/base/graphql/base_mutation.rb +23 -0
- data/lib/groovestack/base/graphql/base_subscription.rb +13 -0
- data/lib/groovestack/base/graphql/documentation.rb +29 -0
- data/lib/groovestack/base/graphql/helpers.rb +152 -0
- data/lib/groovestack/base/graphql/providers/react_admin/resource.rb +146 -0
- data/lib/groovestack/base/graphql/providers/react_admin/types.rb +17 -0
- data/lib/groovestack/base/graphql/subscriptions/event_handler.rb +26 -0
- data/lib/groovestack/base/graphql/tracers/atomic_multiplex_transaction.rb +63 -0
- data/lib/groovestack/base/graphql/types.rb +103 -0
- data/lib/groovestack/base/listeners.rb +85 -0
- data/lib/groovestack/base/pub_sub.rb +25 -0
- data/lib/groovestack/base/puma/plugin/core_cron.rb +62 -0
- data/lib/groovestack/base/railtie.rb +114 -0
- data/lib/groovestack/base/utilities/string.rb +29 -0
- data/lib/groovestack/base/version.rb +7 -0
- data/lib/groovestack/base.rb +142 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 21487aa70317a98995fd2f11993f6679972cfbedfb0727e78abe72c8db9b352c
|
4
|
+
data.tar.gz: e673c4e3a53e23f6c16cc4337c7bb38ff3684a74d87b8c6aab3b2b70170039e5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f7d41d9614e4dba925d3cdee07c0978230538219e0e8caef9ffeb0bfc297f938a9d64f7afb00106c299a7d835a591b724fbd15ddfd3624c225bd2a4feac7053c
|
7
|
+
data.tar.gz: c843c0f90a34b5bd64460fb6e872684d46d1bee0daf0faef9045fe4a791cbfda48c97225bbf815b43fef3d520544c69a630efb15598bcf2b8491e21d1e36537a
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-graphql
|
3
|
+
- rubocop-rails
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
NewCops: enable
|
7
|
+
TargetRubyVersion: 3.1.3
|
8
|
+
|
9
|
+
GraphQL/FieldDescription:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
GraphQL/ObjectDescription:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Metrics/BlockLength:
|
16
|
+
Exclude:
|
17
|
+
- spec/**/*
|
18
|
+
- '*.gemspec'
|
19
|
+
|
20
|
+
Metrics/MethodLength:
|
21
|
+
Max: 20
|
22
|
+
|
23
|
+
# TODO: reenable and replace puts / logs with global, configurable logger
|
24
|
+
Rails/Output:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/Documentation:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/RedundantConstantBase:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
# Style/StringLiterals:
|
36
|
+
# Enabled: true
|
37
|
+
# EnforcedStyle: double_quotes
|
38
|
+
|
39
|
+
# Style/StringLiteralsInInterpolation:
|
40
|
+
# Enabled: true
|
41
|
+
# EnforcedStyle: double_quotes
|
42
|
+
|
43
|
+
# Layout/LineLength:
|
44
|
+
# Max: 120
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Member
|
6
|
+
module BaseDSLMethods
|
7
|
+
def default_graphql_name
|
8
|
+
@default_graphql_name ||= begin
|
9
|
+
if name.nil?
|
10
|
+
raise ::GraphQL::RequiredImplementationMissingError,
|
11
|
+
'Anonymous class should declare a `graphql_name`'
|
12
|
+
end
|
13
|
+
|
14
|
+
# ex name: "GraphQL::User::Type"
|
15
|
+
# ex name: "GraphQL::Admin::User::Type"
|
16
|
+
|
17
|
+
graphql_name = name.split('GraphQL').last.split('Types::').last.split('::Type').first.gsub('::', '')
|
18
|
+
# TODO: refactor
|
19
|
+
# assumption here that graphql schema has an admin namespace.
|
20
|
+
graphql_name.gsub('Admin', '')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/config.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler.require :default, :development
|
7
|
+
|
8
|
+
# Combustion.schema_format = :sql
|
9
|
+
# Combustion.initialize! :active_record
|
10
|
+
|
11
|
+
# # Combustion.initialize! :all
|
12
|
+
# run Combustion::Application
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/groovestack/base/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'groovestack-base'
|
7
|
+
spec.version = Groovestack::Base::VERSION
|
8
|
+
spec.authors = ['Darren Rush']
|
9
|
+
spec.email = ['dlrush@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Shared extensions for CORE modules'
|
12
|
+
spec.description = 'Groovestack::Base defines reusable extensions for the CORE Platform.'
|
13
|
+
spec.post_install_message = 'Groovestack::Base installed'
|
14
|
+
|
15
|
+
spec.homepage = 'https://github.com/talysto/groovestack-core/'
|
16
|
+
spec.required_ruby_version = '>= 3.1.0'
|
17
|
+
|
18
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
19
|
+
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/talysto/groovestack-core/'
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/talysto/groovestack-core/'
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_dependency 'activerecord', '~> 7.0'
|
34
|
+
spec.add_dependency 'dry-configurable', '~> 1.0'
|
35
|
+
spec.add_dependency 'graphql', '~> 2.5'
|
36
|
+
spec.add_dependency 'pg', '~> 1.0'
|
37
|
+
spec.add_dependency 'pg_lock', '~> 1.0'
|
38
|
+
spec.add_dependency 'puma', '~> 5.0'
|
39
|
+
|
40
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module ActiveRecord
|
6
|
+
module Authorization
|
7
|
+
module FieldsForSerialization
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# required for memoization during graphql serialization
|
11
|
+
def authorized_fields_for_serialization(user)
|
12
|
+
@authorized_fields_for_serialization ||= Pundit.policy!(user, self).permitted_attributes_for_show
|
13
|
+
@authorized_fields_for_serialization
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
class BaseMutation < ::GraphQL::Schema::Mutation
|
7
|
+
argument_class ::Groovestack::Base::GraphQL::Types::BaseArgument
|
8
|
+
|
9
|
+
def resolve(**args)
|
10
|
+
perform(**args)
|
11
|
+
rescue StandardError => e
|
12
|
+
Groovestack::Base.notify_error(self.class, e)
|
13
|
+
|
14
|
+
raise e
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform(**args)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
class BaseSubscription < ::GraphQL::Schema::Subscription
|
7
|
+
argument_class ::Groovestack::Base::GraphQL::Types::BaseArgument
|
8
|
+
field_class ::Groovestack::Base::GraphQL::Types::BaseField
|
9
|
+
object_class ::Groovestack::Base::GraphQL::Types::BaseObject
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Groovestack
|
6
|
+
module Base
|
7
|
+
module GraphQL
|
8
|
+
module Documentation
|
9
|
+
# rubocop:disable Style/OpenStructUse
|
10
|
+
Arguments = OpenStruct.new({
|
11
|
+
id: 'a unique record identifier',
|
12
|
+
page: 'page number of the paginated results',
|
13
|
+
per_page: 'number of records per page of the paginated results',
|
14
|
+
sort_field: 'field to sort the results by (i.e. created_at)',
|
15
|
+
sort_order: 'order with which to sort the results (i.e. ASC, DESC)',
|
16
|
+
filter: 'hash of parameters by which to filter the results (i.e. { ids: [1,2,3] })'
|
17
|
+
})
|
18
|
+
Fields = OpenStruct.new({
|
19
|
+
created_at: 'time of record creation',
|
20
|
+
id: 'a unique record identifier',
|
21
|
+
relation_count: 'total number of records in a given scope',
|
22
|
+
updated_at: 'time of last record update'
|
23
|
+
})
|
24
|
+
|
25
|
+
# rubocop:enable Style/OpenStructUse
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
module Helpers
|
7
|
+
module Types
|
8
|
+
module StatusEventVirtualAttributes
|
9
|
+
def all_events
|
10
|
+
object.class.aasm.events.map { |event| event.name.to_s }
|
11
|
+
end
|
12
|
+
|
13
|
+
def permitted_events
|
14
|
+
object.aasm.events(permitted: true).map { |event| event.name.to_s }
|
15
|
+
end
|
16
|
+
|
17
|
+
def status_events
|
18
|
+
all = all_events
|
19
|
+
permitted = permitted_events
|
20
|
+
|
21
|
+
all.map do |event|
|
22
|
+
{ name: event.titleize, key: event, enabled: permitted.include?(event) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Typified
|
28
|
+
extend ActiveSupport::Concern
|
29
|
+
|
30
|
+
included do
|
31
|
+
field :type, String, null: true, resolver_method: :object_type
|
32
|
+
|
33
|
+
def object_type
|
34
|
+
object.class.to_s if object.respond_to?(:class)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Mutations
|
41
|
+
module StatusEvents
|
42
|
+
def trigger_status_event!(obj:, attrs:, event:, args: nil, authorization_policy: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
43
|
+
# NOTE: args spread requires keys to be symbols, but by default
|
44
|
+
# they are passed as strings. This is why we use `symbolize_keys` here
|
45
|
+
raise ::GraphQL::ExecutionError, 'event not present' if event.blank?
|
46
|
+
|
47
|
+
event = event.gsub('!', '').to_sym
|
48
|
+
|
49
|
+
if attrs.present?
|
50
|
+
raise ::GraphQL::ExecutionError,
|
51
|
+
'Cannot update multiple attributes when firing instance methods'
|
52
|
+
end
|
53
|
+
unless obj.aasm.events.map(&:name).include?(event)
|
54
|
+
raise ::GraphQL::ExecutionError,
|
55
|
+
"#{event.capitalize} unavailable"
|
56
|
+
end
|
57
|
+
unless authorization_policy.nil? || authorization_policy.permitted_aasm_events.include?(event)
|
58
|
+
raise ::GraphQL::ExecutionError,
|
59
|
+
"Unauthorized not allowed to #{event} this #{obj.class}"
|
60
|
+
end
|
61
|
+
|
62
|
+
event = :"#{event}!"
|
63
|
+
|
64
|
+
if args.present? && args.is_a?(Array)
|
65
|
+
obj.send(event.to_s, *args)
|
66
|
+
elsif args.present? && args.is_a?(Hash)
|
67
|
+
obj.send(event.to_s, **args.symbolize_keys!)
|
68
|
+
else
|
69
|
+
obj.send(event.to_s)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods
|
75
|
+
def trigger_instance_method!(obj:, attrs:, instance_method:, args: nil, authorization_policy: nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
76
|
+
raise GraphQL::ExecutionError, 'instance_method not present' if instance_method.blank?
|
77
|
+
|
78
|
+
instance_method = instance_method.to_sym
|
79
|
+
|
80
|
+
raise ::GraphQL::ExecutionError, 'instance_method undefined' unless obj.respond_to?(instance_method)
|
81
|
+
|
82
|
+
if attrs.present?
|
83
|
+
raise ::GraphQL::ExecutionError,
|
84
|
+
'Cannot update multiple attributes when triggering instance method'
|
85
|
+
end
|
86
|
+
unless authorization_policy.nil? || authorization_policy.permitted_instance_methods.include?(instance_method) # rubocop:disable Layout/LineLength
|
87
|
+
raise ::GraphQL::ExecutionError,
|
88
|
+
"Unauthorized not allowed to trigger #{instance_method} for this #{obj.class}"
|
89
|
+
end
|
90
|
+
|
91
|
+
if args.present? && args.is_a?(Array)
|
92
|
+
obj.send(instance_method.to_s, *args)
|
93
|
+
elsif args.present? && args.is_a?(Hash)
|
94
|
+
obj.send(instance_method.to_s, **args.symbolize_keys!)
|
95
|
+
else
|
96
|
+
obj.send(instance_method.to_s)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module Controller
|
103
|
+
extend ActiveSupport::Concern
|
104
|
+
|
105
|
+
included do # rubocop:disable Metrics/BlockLength
|
106
|
+
private
|
107
|
+
|
108
|
+
def operation_name
|
109
|
+
params[:operationName]
|
110
|
+
end
|
111
|
+
|
112
|
+
def query
|
113
|
+
params[:query]
|
114
|
+
end
|
115
|
+
|
116
|
+
def variables
|
117
|
+
prepare_variables(params[:variables])
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handle variables in form data, JSON body, or a blank value
|
121
|
+
def prepare_variables(variables_param)
|
122
|
+
case variables_param
|
123
|
+
when String
|
124
|
+
if variables_param.present?
|
125
|
+
JSON.parse(variables_param) || {}
|
126
|
+
else
|
127
|
+
{}
|
128
|
+
end
|
129
|
+
when Hash
|
130
|
+
variables_param
|
131
|
+
when ActionController::Parameters
|
132
|
+
variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
|
133
|
+
when nil
|
134
|
+
{}
|
135
|
+
else
|
136
|
+
raise ArgumentError, "Unexpected parameter: #{variables_param}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def handle_error_in_development(err)
|
141
|
+
logger.error err.message
|
142
|
+
logger.error err.backtrace.join('\/n')
|
143
|
+
|
144
|
+
render json: { errors: [{ message: err.message, backtrace: err.backtrace }], data: {} },
|
145
|
+
status: :internal_server_error
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: refactor according to rubocop suggestions
|
4
|
+
|
5
|
+
module Groovestack
|
6
|
+
module Base
|
7
|
+
module GraphQL
|
8
|
+
module Providers
|
9
|
+
module ReactAdmin
|
10
|
+
module Resource # rubocop:disable Metrics/ModuleLength
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
class_methods do # rubocop:disable Metrics/BlockLength
|
14
|
+
# TODO: make authorize default to true
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
17
|
+
def react_admin_resource(entity,
|
18
|
+
class_name: nil,
|
19
|
+
graphql_path: nil,
|
20
|
+
graphql_type: nil,
|
21
|
+
graphql_filter: nil,
|
22
|
+
authorize: false,
|
23
|
+
visibility_permission: nil,
|
24
|
+
policy: nil,
|
25
|
+
camelize: ::Groovestack::Base.config.graphql.camelize,
|
26
|
+
**args)
|
27
|
+
# NOTE: class_name is only required if a custom _base_scope is not defined
|
28
|
+
# NOTE graphql_path is only required to override the default graphql path
|
29
|
+
|
30
|
+
graphql_namespace = graphql_path&.dup&.concat('::')
|
31
|
+
entity_model_name = entity.to_s.classify
|
32
|
+
entity_class = class_name || entity_model_name
|
33
|
+
entity_type = (graphql_type || "#{graphql_namespace}#{entity_model_name}::Type").constantize
|
34
|
+
entity_filter_type = (graphql_filter || "#{graphql_namespace}#{entity_model_name}::Filter").constantize
|
35
|
+
except = args.delete(:except) || []
|
36
|
+
policy ||= "#{entity_class}Policy"
|
37
|
+
base_scope = begin
|
38
|
+
entity_class.constantize.unscoped
|
39
|
+
rescue StandardError
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# resolver_method for Record
|
44
|
+
|
45
|
+
unless except.include?(:find)
|
46
|
+
define_method entity_model_name.to_sym do |id:|
|
47
|
+
scope = if authorize
|
48
|
+
policy.constantize::ShowScope.new(context[:current_user],
|
49
|
+
base_scope).resolve
|
50
|
+
else
|
51
|
+
base_scope
|
52
|
+
end
|
53
|
+
scope.find id
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# resolver_method for Collection
|
58
|
+
|
59
|
+
unless except.include?(:collection)
|
60
|
+
define_method entity do |page: nil, per_page: nil, **attrs|
|
61
|
+
scope = if authorize
|
62
|
+
policy.constantize::IndexScope.new(context[:current_user],
|
63
|
+
base_scope).resolve
|
64
|
+
else
|
65
|
+
base_scope
|
66
|
+
end
|
67
|
+
scope = send("#{entity}_scope", **attrs, base_scope: scope)
|
68
|
+
scope = scope.offset(page * per_page).limit(per_page) if page.present? && scope.respond_to?(:offset)
|
69
|
+
scope
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# resolver_method for Collection meta
|
74
|
+
|
75
|
+
unless except.include?(:collection_meta)
|
76
|
+
define_method :"#{entity}_meta" do |page: nil, per_page: nil, **attrs| # rubocop:disable Lint/UnusedBlockArgument
|
77
|
+
scope = if authorize
|
78
|
+
policy.constantize::IndexScope.new(context[:current_user],
|
79
|
+
base_scope).resolve
|
80
|
+
else
|
81
|
+
base_scope
|
82
|
+
end
|
83
|
+
{ count: send("#{entity}_scope", **attrs, base_scope: scope).size }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Record
|
88
|
+
|
89
|
+
unless except.include?(:find)
|
90
|
+
field entity_model_name.to_sym, entity_type,
|
91
|
+
null: true,
|
92
|
+
visibility_permission: visibility_permission,
|
93
|
+
resolver_method: entity_model_name.to_sym,
|
94
|
+
description: "Find #{entity_class}." do
|
95
|
+
argument :id, ::GraphQL::Types::ID, required: true, description: Documentation::Arguments.id
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Collection
|
100
|
+
|
101
|
+
unless except.include?(:collection)
|
102
|
+
field :"all_#{entity.to_s.underscore}",
|
103
|
+
type: [entity_type],
|
104
|
+
null: false,
|
105
|
+
camelize: camelize,
|
106
|
+
visibility_permission: visibility_permission,
|
107
|
+
resolver_method: entity do
|
108
|
+
argument :page, ::GraphQL::Types::Int, required: false, description: Documentation::Arguments.page
|
109
|
+
argument :per_page, ::GraphQL::Types::Int, required: false,
|
110
|
+
description: Documentation::Arguments.per_page
|
111
|
+
argument :sort_field, ::GraphQL::Types::String, required: false,
|
112
|
+
description: Documentation::Arguments.sort_field
|
113
|
+
argument :sort_order, ::GraphQL::Types::String, required: false,
|
114
|
+
description: Documentation::Arguments.sort_order
|
115
|
+
argument :filter, entity_filter_type, required: false, description: Documentation::Arguments.filter
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Collection meta
|
120
|
+
|
121
|
+
return if except.include?(:collection_meta)
|
122
|
+
|
123
|
+
field :"_all_#{entity.to_s.underscore}_meta",
|
124
|
+
type: ::Groovestack::Base::GraphQL::Providers::ReactAdmin::Types::RAListMetadata,
|
125
|
+
camelize: camelize,
|
126
|
+
null: true,
|
127
|
+
visibility_permission: visibility_permission,
|
128
|
+
resolver_method: :"#{entity}_meta" do
|
129
|
+
argument :page, ::GraphQL::Types::Int, required: false, description: Documentation::Arguments.page
|
130
|
+
argument :per_page, ::GraphQL::Types::Int, required: false,
|
131
|
+
description: Documentation::Arguments.per_page
|
132
|
+
argument :sort_field, ::GraphQL::Types::String, required: false,
|
133
|
+
description: Documentation::Arguments.sort_field
|
134
|
+
argument :sort_order, ::GraphQL::Types::String, required: false,
|
135
|
+
description: Documentation::Arguments.sort_order
|
136
|
+
argument :filter, entity_filter_type, required: false, description: Documentation::Arguments.filter
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
module Providers
|
7
|
+
module ReactAdmin
|
8
|
+
module Types
|
9
|
+
class RAListMetadata < ::GraphQL::Schema::Object
|
10
|
+
field :count, Int, null: false, description: Documentation::Fields.relation_count
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
module Subscriptions
|
7
|
+
class EventHandler
|
8
|
+
def self.trigger(subscription, args, event, kwargs)
|
9
|
+
triggered = false
|
10
|
+
|
11
|
+
::GraphQL::Schema.descendants.each do |schema|
|
12
|
+
subscription_type = schema.types['Subscription'] || schema.types['SubscriptionType']
|
13
|
+
next if subscription_type.blank?
|
14
|
+
next unless schema.get_fields(subscription_type).keys.include?(subscription.to_s)
|
15
|
+
|
16
|
+
triggered = true
|
17
|
+
schema.subscriptions.trigger(subscription, args, event, **kwargs)
|
18
|
+
end
|
19
|
+
|
20
|
+
puts 'app schema not defined' unless triggered
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Base
|
5
|
+
module GraphQL
|
6
|
+
module Tracers
|
7
|
+
# The AtmoicMultiplexTransaction module provides a mechanism to execute GraphQL multiplex queries
|
8
|
+
# within a database transaction. If any of the queries in the multiplex is a mutation, the entire
|
9
|
+
# multiplex is executed within a transaction. If an error occurs during the execution of a mutation,
|
10
|
+
# the transaction is rolled back, and the errors are returned with null data.
|
11
|
+
#
|
12
|
+
# Methods:
|
13
|
+
# - execute_multiplex(multiplex:): Executes the provided multiplex within a transaction if any of the
|
14
|
+
# queries is a mutation. If an error occurs during the execution of a mutation, the transaction is
|
15
|
+
# rolled back, and the errors are returned with null data.
|
16
|
+
#
|
17
|
+
|
18
|
+
# NOTE: This module assumes that the multiplex object responds to `queries` and each query
|
19
|
+
# responds to `mutation?`.
|
20
|
+
|
21
|
+
module AtomicMultiplexTransaction
|
22
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
23
|
+
def execute_multiplex(multiplex:)
|
24
|
+
is_mutation = multiplex.queries.any?(&:mutation?)
|
25
|
+
|
26
|
+
return yield unless is_mutation
|
27
|
+
|
28
|
+
results = nil
|
29
|
+
rollback = false
|
30
|
+
|
31
|
+
begin
|
32
|
+
::ActiveRecord::Base.transaction do
|
33
|
+
results = yield
|
34
|
+
|
35
|
+
rollback = results.any? do |result|
|
36
|
+
result.is_a?(::GraphQL::Query::Result) && result.to_h['errors'].present?
|
37
|
+
end
|
38
|
+
|
39
|
+
raise ::ActiveRecord::Rollback if rollback
|
40
|
+
|
41
|
+
results
|
42
|
+
end
|
43
|
+
rescue ::ActiveRecord::Rollback
|
44
|
+
rollback = true
|
45
|
+
end
|
46
|
+
|
47
|
+
return results unless rollback
|
48
|
+
|
49
|
+
# IF rollback, extract errors and return null for data
|
50
|
+
|
51
|
+
results.map do |result|
|
52
|
+
::GraphQL::Query::Result.new(
|
53
|
+
query: result.query,
|
54
|
+
values: result.to_h.merge('data' => nil)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|