forest_admin_agent 1.0.0.pre.beta.21
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 +3 -0
- data/CHANGELOG.md +5 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/forest_admin +1 -0
- data/forest_admin_agent.gemspec +48 -0
- data/lib/forest_admin_agent/auth/auth_manager.rb +50 -0
- data/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +62 -0
- data/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb +42 -0
- data/lib/forest_admin_agent/auth/oauth2/oidc_config.rb +29 -0
- data/lib/forest_admin_agent/auth/oidc_client_manager.rb +71 -0
- data/lib/forest_admin_agent/builder/agent_factory.rb +77 -0
- data/lib/forest_admin_agent/facades/container.rb +23 -0
- data/lib/forest_admin_agent/facades/whitelist.rb +13 -0
- data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +16 -0
- data/lib/forest_admin_agent/http/Exceptions/http_exception.rb +17 -0
- data/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +15 -0
- data/lib/forest_admin_agent/http/forest_admin_api_requester.rb +28 -0
- data/lib/forest_admin_agent/http/router.rb +52 -0
- data/lib/forest_admin_agent/routes/abstract_authenticated_route.rb +25 -0
- data/lib/forest_admin_agent/routes/abstract_related_route.rb +12 -0
- data/lib/forest_admin_agent/routes/abstract_route.rb +27 -0
- data/lib/forest_admin_agent/routes/resources/count.rb +41 -0
- data/lib/forest_admin_agent/routes/resources/delete.rb +51 -0
- data/lib/forest_admin_agent/routes/resources/list.rb +38 -0
- data/lib/forest_admin_agent/routes/resources/related/associate_related.rb +63 -0
- data/lib/forest_admin_agent/routes/resources/related/count_related.rb +56 -0
- data/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +97 -0
- data/lib/forest_admin_agent/routes/resources/related/list_related.rb +54 -0
- data/lib/forest_admin_agent/routes/resources/related/update_related.rb +102 -0
- data/lib/forest_admin_agent/routes/resources/show.rb +44 -0
- data/lib/forest_admin_agent/routes/resources/store.rb +51 -0
- data/lib/forest_admin_agent/routes/resources/update.rb +42 -0
- data/lib/forest_admin_agent/routes/security/authentication.rb +95 -0
- data/lib/forest_admin_agent/routes/system/health_check.rb +22 -0
- data/lib/forest_admin_agent/serializer/forest_serializer.rb +176 -0
- data/lib/forest_admin_agent/serializer/forest_serializer_override.rb +103 -0
- data/lib/forest_admin_agent/services/ip_whitelist.rb +100 -0
- data/lib/forest_admin_agent/services/logger_service.rb +20 -0
- data/lib/forest_admin_agent/utils/condition_tree_parser.rb +57 -0
- data/lib/forest_admin_agent/utils/error_messages.rb +38 -0
- data/lib/forest_admin_agent/utils/id.rb +48 -0
- data/lib/forest_admin_agent/utils/query_string_parser.rb +89 -0
- data/lib/forest_admin_agent/utils/schema/frontend_filterable.rb +73 -0
- data/lib/forest_admin_agent/utils/schema/generator_collection.rb +35 -0
- data/lib/forest_admin_agent/utils/schema/generator_field.rb +183 -0
- data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +103 -0
- data/lib/forest_admin_agent/version.rb +3 -0
- data/lib/forest_admin_agent.rb +11 -0
- data/sig/forest_admin_agent/auth/auth_manager.rbs +14 -0
- data/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs +16 -0
- data/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs +15 -0
- data/sig/forest_admin_agent/auth/oidc_client_manager.rbs +15 -0
- data/sig/forest_admin_agent/builder/agent_factory.rbs +21 -0
- data/sig/forest_admin_agent/facades/container.rbs +9 -0
- data/sig/forest_admin_agent/http/router.rbs +9 -0
- data/sig/forest_admin_agent/routes/abstract_route.rbs +12 -0
- data/sig/forest_admin_agent/routes/security/authentication.rbs +14 -0
- data/sig/forest_admin_agent/routes/system/health_check.rbs +10 -0
- data/sig/forest_admin_agent.rbs +4 -0
- metadata +279 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require 'active_support/time'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Utils
|
6
|
+
class QueryStringParser
|
7
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
8
|
+
include ForestAdminDatasourceToolkit::Components
|
9
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
10
|
+
|
11
|
+
DEFAULT_ITEMS_PER_PAGE = '15'.freeze
|
12
|
+
DEFAULT_PAGE_TO_SKIP = '1'.freeze
|
13
|
+
|
14
|
+
def self.parse_condition_tree(collection, args)
|
15
|
+
filters = begin
|
16
|
+
args.dig(:params, :data, :attributes, :all_records_subset_query, :filters) ||
|
17
|
+
args.dig(:params, :filters) || args.dig(:params, :filter)
|
18
|
+
rescue StandardError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
return if filters.nil?
|
23
|
+
|
24
|
+
filters = JSON.parse(filters) if filters.is_a? String
|
25
|
+
ConditionTreeParser.from_plain_object(collection, filters)
|
26
|
+
# TODO: ConditionTreeValidator::validate($conditionTree, $collection);
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_caller(args)
|
30
|
+
unless args.dig(:headers, 'HTTP_AUTHORIZATION')
|
31
|
+
# TODO: replace by http exception
|
32
|
+
raise ForestException, 'You must be logged in to access at this resource.'
|
33
|
+
end
|
34
|
+
|
35
|
+
timezone = args[:params]['timezone']
|
36
|
+
raise ForestException, 'Missing timezone' unless timezone
|
37
|
+
|
38
|
+
raise ForestException, "Invalid timezone: #{timezone}" unless Time.find_zone(timezone)
|
39
|
+
|
40
|
+
token = args[:headers]['HTTP_AUTHORIZATION'].split[1]
|
41
|
+
token_data = JWT.decode(
|
42
|
+
token,
|
43
|
+
Facades::Container.cache(:auth_secret),
|
44
|
+
true,
|
45
|
+
{ algorithm: 'HS256' }
|
46
|
+
)[0]
|
47
|
+
token_data.delete('exp')
|
48
|
+
token_data[:timezone] = timezone
|
49
|
+
|
50
|
+
Caller.new(**token_data.transform_keys(&:to_sym))
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_projection(collection, args)
|
54
|
+
fields = args.dig(:params, :fields, collection.name) || ''
|
55
|
+
|
56
|
+
return ProjectionFactory.all(collection) unless fields != '' && !fields.nil?
|
57
|
+
|
58
|
+
fields = fields.split(',').map do |field_name|
|
59
|
+
column = collection.fields[field_name.strip]
|
60
|
+
column.type == 'Column' ? field_name.strip : "#{field_name.strip}:#{args[:params][:fields][field_name.strip]}"
|
61
|
+
end
|
62
|
+
|
63
|
+
Projection.new(fields)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.parse_projection_with_pks(collection, args)
|
67
|
+
projection = parse_projection(collection, args)
|
68
|
+
|
69
|
+
projection.with_pks(collection)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.parse_pagination(args)
|
73
|
+
items_per_pages = args.dig(:params, :data, :attributes, :all_records_subset_query, :size) ||
|
74
|
+
args.dig(:params, :page, :size) || DEFAULT_ITEMS_PER_PAGE
|
75
|
+
|
76
|
+
page = args.dig(:params, :data, :attributes, :all_records_subset_query, :number) ||
|
77
|
+
args.dig(:params, :page, :number) || DEFAULT_PAGE_TO_SKIP
|
78
|
+
|
79
|
+
unless !items_per_pages.to_s.match(/\A[+]?\d+\z/).nil? || !page.to_s.match(/\A[+]?\d+\z/).nil?
|
80
|
+
raise ForestException, "Invalid pagination [limit: #{items_per_pages}, skip: #{page}]"
|
81
|
+
end
|
82
|
+
|
83
|
+
offset = (page.to_i - 1) * items_per_pages.to_i
|
84
|
+
|
85
|
+
Page.new(offset: offset, limit: items_per_pages.to_i)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Utils
|
6
|
+
module Schema
|
7
|
+
class FrontendFilterable
|
8
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
|
9
|
+
|
10
|
+
BASE_OPERATORS = [
|
11
|
+
Operators::EQUAL, Operators::NOT_EQUAL, Operators::PRESENT, Operators::BLANK
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
BASE_DATEONLY_OPERATORS = [
|
15
|
+
Operators::TODAY,
|
16
|
+
Operators::YESTERDAY,
|
17
|
+
Operators::PREVIOUS_X_DAYS,
|
18
|
+
Operators::PREVIOUS_WEEK,
|
19
|
+
Operators::PREVIOUS_MONTH,
|
20
|
+
Operators::PREVIOUS_QUARTER,
|
21
|
+
Operators::PREVIOUS_YEAR,
|
22
|
+
Operators::PREVIOUS_X_DAYS_TO_DATE,
|
23
|
+
Operators::PREVIOUS_WEEK_TO_DATE,
|
24
|
+
Operators::PREVIOUS_MONTH_TO_DATE,
|
25
|
+
Operators::PREVIOUS_QUARTER_TO_DATE,
|
26
|
+
Operators::PREVIOUS_YEAR_TO_DATE,
|
27
|
+
Operators::PAST,
|
28
|
+
Operators::FUTURE,
|
29
|
+
Operators::BEFORE_X_HOURS_AGO,
|
30
|
+
Operators::AFTER_X_HOURS_AGO,
|
31
|
+
Operators::BEFORE,
|
32
|
+
Operators::AFTER
|
33
|
+
].freeze
|
34
|
+
|
35
|
+
DATE_OPERATORS = BASE_OPERATORS + BASE_DATEONLY_OPERATORS
|
36
|
+
|
37
|
+
OPERATOR_BY_TYPE = {
|
38
|
+
'Binary' => BASE_OPERATORS,
|
39
|
+
'Boolean' => BASE_OPERATORS,
|
40
|
+
'Date' => DATE_OPERATORS,
|
41
|
+
'Dateonly' => DATE_OPERATORS,
|
42
|
+
'Uuid' => BASE_OPERATORS,
|
43
|
+
'Enum' => BASE_OPERATORS + [Operators::IN],
|
44
|
+
'Number' => BASE_OPERATORS + [Operators::IN, Operators::GREATER_THAN, Operators::LESS_THAN],
|
45
|
+
'Timeonly' => BASE_OPERATORS + [Operators::GREATER_THAN, Operators::LESS_THAN],
|
46
|
+
'String' => BASE_OPERATORS +
|
47
|
+
[
|
48
|
+
Operators::IN,
|
49
|
+
Operators::STARTS_WITH,
|
50
|
+
Operators::ENDS_WITH,
|
51
|
+
Operators::CONTAINS,
|
52
|
+
Operators::NOT_CONTAINS
|
53
|
+
],
|
54
|
+
'Json' => []
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
def self.filterable?(type, supported_operators = [])
|
58
|
+
needed_operators = get_required_operators(type)
|
59
|
+
|
60
|
+
!needed_operators.empty? && needed_operators.all? { |operator| supported_operators.include?(operator) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.get_required_operators(type)
|
64
|
+
return OPERATOR_BY_TYPE[type] if type.is_a?(String) && OPERATOR_BY_TYPE.key?(type)
|
65
|
+
|
66
|
+
return ['Includes_All'] if type.is_a? Array
|
67
|
+
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class GeneratorCollection
|
5
|
+
include ForestAdminDatasourceToolkit::Utils
|
6
|
+
|
7
|
+
def self.build_schema(collection)
|
8
|
+
{
|
9
|
+
actions: {},
|
10
|
+
fields: build_fields(collection),
|
11
|
+
icon: nil,
|
12
|
+
integration: nil,
|
13
|
+
isReadOnly: collection.fields.all? { |_k, field| field.type != 'Column' || field.is_read_only },
|
14
|
+
isSearchable: true,
|
15
|
+
isVirtual: false,
|
16
|
+
name: collection.name,
|
17
|
+
onlyForRelationships: false,
|
18
|
+
paginationType: 'page',
|
19
|
+
segments: {}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.build_fields(collection)
|
24
|
+
fields = collection.fields.select do |name, _field|
|
25
|
+
!ForestAdminDatasourceToolkit::Utils::Schema.foreign_key?(collection, name) ||
|
26
|
+
ForestAdminDatasourceToolkit::Utils::Schema.primary_key?(collection, name)
|
27
|
+
end
|
28
|
+
|
29
|
+
fields.map { |name, _field| GeneratorField.build_schema(collection, name) }
|
30
|
+
.sort_by { |v| v[:field] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Utils
|
3
|
+
module Schema
|
4
|
+
class GeneratorField
|
5
|
+
RELATION_MAP = {
|
6
|
+
'ManyToMany' => 'BelongsToMany',
|
7
|
+
'ManyToOne' => 'BelongsTo',
|
8
|
+
'OneToMany' => 'HasMany',
|
9
|
+
'OneToOne' => 'HasOne'
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.build_schema(collection, name)
|
13
|
+
type = collection.fields[name].type
|
14
|
+
|
15
|
+
case type
|
16
|
+
when 'Column'
|
17
|
+
schema = build_column_schema(collection, name)
|
18
|
+
when 'ManyToOne', 'OneToMany', 'ManyToMany', 'OneToOne'
|
19
|
+
schema = build_relation_schema(collection, name)
|
20
|
+
end
|
21
|
+
|
22
|
+
schema.sort_by { |k, _v| k }.to_h
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_column_schema(collection, name)
|
29
|
+
column = collection.fields[name]
|
30
|
+
is_foreign_key = ForestAdminDatasourceToolkit::Utils::Schema.foreign_key?(collection, name)
|
31
|
+
|
32
|
+
{
|
33
|
+
defaultValue: column.default_value,
|
34
|
+
enums: column.enum_values.sort,
|
35
|
+
field: name,
|
36
|
+
integration: nil,
|
37
|
+
inverseOf: nil,
|
38
|
+
# isFilterable: FrontendFilterable.filterable?(column.column_type, column.filter_operators),
|
39
|
+
isFilterable: true, # TODO: remove when implementing operators decorators
|
40
|
+
isPrimaryKey: column.is_primary_key,
|
41
|
+
|
42
|
+
# When a column is a foreign key, it is readonly.
|
43
|
+
# This may sound counter-intuitive: it is so that the user don't have two fields which
|
44
|
+
# allow updating the same foreign key in the detail-view form (fk + many to one)
|
45
|
+
isReadOnly: is_foreign_key || column.is_read_only,
|
46
|
+
isRequired: column.validations.any? { |v| v[:operator] == 'Present' },
|
47
|
+
isSortable: column.is_sortable,
|
48
|
+
isVirtual: false,
|
49
|
+
reference: nil,
|
50
|
+
type: convert_column_type(column.column_type),
|
51
|
+
validations: [] # TODO: FrontendValidationUtils.convertValidationList(column),
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def convert_column_type(type)
|
56
|
+
return type if type.instance_of? String
|
57
|
+
|
58
|
+
return [convert_column_type(type.first)] if type.instance_of? Array
|
59
|
+
|
60
|
+
{
|
61
|
+
fields: type.map do |key, sub_type|
|
62
|
+
{
|
63
|
+
field: key,
|
64
|
+
type: convert_column_type(sub_type)
|
65
|
+
}
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def foreign_collection_filterable?
|
71
|
+
# TODO: implement FrontendFilterable before
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_many_to_many_schema(relation, collection, foreign_collection, base_schema)
|
76
|
+
target_name = relation.foreign_key_target
|
77
|
+
target_field = foreign_collection.fields[target_name]
|
78
|
+
through_schema = collection.datasource.collection(relation.through_collection)
|
79
|
+
foreign_schema = through_schema.fields[relation.foreign_key]
|
80
|
+
origin_key = through_schema.fields[relation.origin_key]
|
81
|
+
|
82
|
+
base_schema.merge(
|
83
|
+
{
|
84
|
+
type: [target_field.column_type],
|
85
|
+
defaultValue: nil,
|
86
|
+
isFilterable: false,
|
87
|
+
isPrimaryKey: false,
|
88
|
+
isRequired: false,
|
89
|
+
isReadOnly: origin_key.is_read_only || foreign_schema.is_read_only,
|
90
|
+
isSortable: true,
|
91
|
+
validations: [],
|
92
|
+
reference: "#{foreign_collection.name}.#{target_name}"
|
93
|
+
}
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_one_to_many_schema(relation, collection, foreign_collection, base_schema)
|
98
|
+
target_name = relation.origin_key_target
|
99
|
+
target_field = collection.fields[target_name]
|
100
|
+
origin_key = foreign_collection.fields[relation.origin_key]
|
101
|
+
|
102
|
+
base_schema.merge(
|
103
|
+
{
|
104
|
+
type: [target_field.column_type],
|
105
|
+
defaultValue: nil,
|
106
|
+
isFilterable: false,
|
107
|
+
isPrimaryKey: false,
|
108
|
+
isRequired: false,
|
109
|
+
isReadOnly: origin_key.is_read_only,
|
110
|
+
isSortable: true,
|
111
|
+
validations: [],
|
112
|
+
reference: "#{foreign_collection.name}.#{target_name}"
|
113
|
+
}
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_one_to_one_schema(relation, collection, foreign_collection, base_schema)
|
118
|
+
target_field = collection.fields[relation.origin_key_target]
|
119
|
+
key_field = foreign_collection.fields[relation.origin_key]
|
120
|
+
|
121
|
+
base_schema.merge(
|
122
|
+
{
|
123
|
+
type: key_field.column_type,
|
124
|
+
defaultValue: nil,
|
125
|
+
isFilterable: foreign_collection_filterable?,
|
126
|
+
isPrimaryKey: false,
|
127
|
+
isRequired: false,
|
128
|
+
isReadOnly: key_field.is_read_only,
|
129
|
+
isSortable: target_field.is_sortable,
|
130
|
+
validations: [],
|
131
|
+
reference: "#{foreign_collection.name}.#{relation.origin_key_target}"
|
132
|
+
}
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_many_to_one_schema(relation, collection, foreign_collection, base_schema)
|
137
|
+
key_field = collection.fields[relation.foreign_key]
|
138
|
+
|
139
|
+
base_schema.merge(
|
140
|
+
{
|
141
|
+
type: key_field.column_type,
|
142
|
+
defaultValue: key_field.default_value,
|
143
|
+
isFilterable: foreign_collection_filterable?,
|
144
|
+
isPrimaryKey: false,
|
145
|
+
isRequired: false, # TODO: check with validations
|
146
|
+
isReadOnly: key_field.is_read_only,
|
147
|
+
isSortable: key_field.is_sortable,
|
148
|
+
validations: [], # TODO: FrontendValidation::convertValidationList(foreignTargetColumn)
|
149
|
+
reference: "#{foreign_collection.name}.#{relation.foreign_key_target}"
|
150
|
+
}
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
def build_relation_schema(collection, name)
|
155
|
+
relation = collection.fields[name]
|
156
|
+
foreign_collection = collection.datasource.collection(relation.foreign_collection)
|
157
|
+
|
158
|
+
relation_schema = {
|
159
|
+
field: name,
|
160
|
+
enums: nil,
|
161
|
+
integration: nil,
|
162
|
+
isReadOnly: nil,
|
163
|
+
isVirtual: false,
|
164
|
+
inverseOf: nil, # TODO: CollectionUtils::getInverseRelation(collection, name)
|
165
|
+
relationship: RELATION_MAP[relation.type]
|
166
|
+
}
|
167
|
+
|
168
|
+
case relation.type
|
169
|
+
when 'ManyToMany'
|
170
|
+
build_many_to_many_schema(relation, collection, foreign_collection, relation_schema)
|
171
|
+
when 'OneToMany'
|
172
|
+
build_one_to_many_schema(relation, collection, foreign_collection, relation_schema)
|
173
|
+
when 'OneToOne'
|
174
|
+
build_one_to_one_schema(relation, collection, foreign_collection, relation_schema)
|
175
|
+
when 'ManyToOne'
|
176
|
+
build_many_to_one_schema(relation, collection, foreign_collection, relation_schema)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ForestAdminAgent
|
5
|
+
module Utils
|
6
|
+
module Schema
|
7
|
+
class SchemaEmitter
|
8
|
+
LIANA_NAME = "forest-rails"
|
9
|
+
|
10
|
+
LIANA_VERSION = "1.0.0-beta.21"
|
11
|
+
|
12
|
+
def self.get_serialized_schema(datasource)
|
13
|
+
schema_path = Facades::Container.cache(:schema_path)
|
14
|
+
if Facades::Container.cache(:is_production)
|
15
|
+
schema = if schema_path && File.exist?(schema_path)
|
16
|
+
JSON.parse(File.read(schema_path), { symbolize_names: true })
|
17
|
+
else
|
18
|
+
# TODO: Logger::log('Warn', 'The .forestadmin-schema.json file doesn\'t exist')
|
19
|
+
{
|
20
|
+
meta: meta(Digest::SHA1.hexdigest('')),
|
21
|
+
collections: {}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
else
|
25
|
+
schema = generate(datasource)
|
26
|
+
hash = Digest::SHA1.hexdigest(schema.to_json)
|
27
|
+
schema = {
|
28
|
+
meta: meta(hash),
|
29
|
+
collections: schema
|
30
|
+
}
|
31
|
+
|
32
|
+
File.write(schema_path, JSON.pretty_generate(schema))
|
33
|
+
end
|
34
|
+
|
35
|
+
serialize(schema)
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
private
|
40
|
+
|
41
|
+
def generate(datasource)
|
42
|
+
datasource.collections
|
43
|
+
.map { |_name, collection| GeneratorCollection.build_schema(collection) }
|
44
|
+
.sort_by { |collection| collection[:name] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def meta(hash)
|
48
|
+
{
|
49
|
+
liana: LIANA_NAME,
|
50
|
+
liana_version: LIANA_VERSION,
|
51
|
+
stack: {
|
52
|
+
engine: 'ruby',
|
53
|
+
engine_version: RUBY_VERSION
|
54
|
+
},
|
55
|
+
schemaFileHash: hash
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def serialize(schema)
|
60
|
+
data = []
|
61
|
+
included = []
|
62
|
+
schema[:collections].each do |collection|
|
63
|
+
collection_actions = collection[:actions]
|
64
|
+
collection_segments = collection[:segments]
|
65
|
+
collection.delete(:actions)
|
66
|
+
collection.delete(:segments)
|
67
|
+
|
68
|
+
included << get_smart_features_by_collection('actions', collection_actions, with_attributes: true)
|
69
|
+
included << get_smart_features_by_collection('segments', collection_segments, with_attributes: true)
|
70
|
+
|
71
|
+
data.push(
|
72
|
+
{
|
73
|
+
id: collection[:name],
|
74
|
+
type: 'collections',
|
75
|
+
attributes: collection,
|
76
|
+
relationships: {
|
77
|
+
actions: { data: get_smart_features_by_collection('actions', collection_actions) },
|
78
|
+
segments: { data: get_smart_features_by_collection('segments', collection_actions) }
|
79
|
+
}
|
80
|
+
}
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
{
|
85
|
+
data: data,
|
86
|
+
included: included.reject!(&:empty?),
|
87
|
+
meta: schema[:meta]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_smart_features_by_collection(type, data, with_attributes: false)
|
92
|
+
smart_features = []
|
93
|
+
data.each do |value|
|
94
|
+
smart_feature = { id: value[:id], type: type }
|
95
|
+
smart_feature[:attributes] = value if with_attributes
|
96
|
+
smart_features << smart_feature
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative 'forest_admin_agent/version'
|
2
|
+
require 'zeitwerk'
|
3
|
+
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.inflector.inflect('oauth2' => 'OAuth2')
|
6
|
+
loader.setup
|
7
|
+
|
8
|
+
module ForestAdminAgent
|
9
|
+
class Error < StandardError; end
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Auth
|
3
|
+
class AuthManager
|
4
|
+
@oidc: ForestAdminAgent::Auth::OidcClientManager
|
5
|
+
|
6
|
+
def initialize: -> void
|
7
|
+
def start: (untyped rendering_id) -> untyped
|
8
|
+
def verify_code_and_generate_token: (untyped params) -> untyped
|
9
|
+
|
10
|
+
private
|
11
|
+
def get_rendering_id_from_state: (untyped state) -> untyped
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Auth
|
3
|
+
module OAuth2
|
4
|
+
class ForestProvider
|
5
|
+
attr_reader rendering_id: Integer
|
6
|
+
|
7
|
+
def initialize: (Integer , Array[String]) -> void
|
8
|
+
def get_resource_owner: -> ForestResourceOwner
|
9
|
+
|
10
|
+
private
|
11
|
+
def create_resource_owner: -> ForestResourceOwner
|
12
|
+
def check_response: -> Array[String]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Auth
|
3
|
+
module OAuth2
|
4
|
+
class ForestResourceOwner
|
5
|
+
@data: Array[String]
|
6
|
+
@rendering_id: Integer
|
7
|
+
|
8
|
+
def initialize: (Array[String] data, Integer rendering_id) -> void
|
9
|
+
def id: -> Integer
|
10
|
+
def expiration_in_seconds: -> Integer
|
11
|
+
def make_jwt: -> String
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Auth
|
3
|
+
class OidcClientManager
|
4
|
+
TTL_CONFIG: Integer
|
5
|
+
|
6
|
+
def make_forest_provider: -> untyped
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def setup_cache: -> { client_id: String, issuer: String, redirect_uri: String }
|
11
|
+
def register: -> Array[String]
|
12
|
+
def render_provider: -> untyped
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Builder
|
3
|
+
class AgentFactory
|
4
|
+
TTL_CONFIG: Integer
|
5
|
+
TTL_SCHEMA: Integer
|
6
|
+
@options: untyped
|
7
|
+
|
8
|
+
attr_reader customizer: untyped
|
9
|
+
attr_reader container: untyped
|
10
|
+
attr_reader has_env_secret: untyped
|
11
|
+
def setup: (Array[string] options) -> untyped
|
12
|
+
def build: -> nil
|
13
|
+
|
14
|
+
private
|
15
|
+
def send_schema: (?force: false) -> nil
|
16
|
+
def build_container: -> untyped
|
17
|
+
def build_cache: -> nil
|
18
|
+
def build_logger: -> untyped
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ForestAdminAgent
|
2
|
+
module Routes
|
3
|
+
module Security
|
4
|
+
class Authentication
|
5
|
+
def setup_routes: -> Authentication
|
6
|
+
def handle_authentication: (?Hash[untyped, untyped] args) -> {content: {authorizationUrl: String}}
|
7
|
+
def handle_authentication_callback: (?Hash[untyped, untyped] args) -> {content: {token: String, tokenData: String}}
|
8
|
+
def handle_authentication_logout: (?Hash[untyped, untyped] _args) -> {content: nil, status: Integer}
|
9
|
+
def auth: -> ForestAdminAgent::Auth::AuthManager
|
10
|
+
def get_and_check_rendering_id: (Hash[untyped, untyped] params) -> Integer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|