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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +35 -0
  5. data/Rakefile +10 -0
  6. data/forest_admin +1 -0
  7. data/forest_admin_agent.gemspec +48 -0
  8. data/lib/forest_admin_agent/auth/auth_manager.rb +50 -0
  9. data/lib/forest_admin_agent/auth/oauth2/forest_provider.rb +62 -0
  10. data/lib/forest_admin_agent/auth/oauth2/forest_resource_owner.rb +42 -0
  11. data/lib/forest_admin_agent/auth/oauth2/oidc_config.rb +29 -0
  12. data/lib/forest_admin_agent/auth/oidc_client_manager.rb +71 -0
  13. data/lib/forest_admin_agent/builder/agent_factory.rb +77 -0
  14. data/lib/forest_admin_agent/facades/container.rb +23 -0
  15. data/lib/forest_admin_agent/facades/whitelist.rb +13 -0
  16. data/lib/forest_admin_agent/http/Exceptions/authentication_open_id_client.rb +16 -0
  17. data/lib/forest_admin_agent/http/Exceptions/http_exception.rb +17 -0
  18. data/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +15 -0
  19. data/lib/forest_admin_agent/http/forest_admin_api_requester.rb +28 -0
  20. data/lib/forest_admin_agent/http/router.rb +52 -0
  21. data/lib/forest_admin_agent/routes/abstract_authenticated_route.rb +25 -0
  22. data/lib/forest_admin_agent/routes/abstract_related_route.rb +12 -0
  23. data/lib/forest_admin_agent/routes/abstract_route.rb +27 -0
  24. data/lib/forest_admin_agent/routes/resources/count.rb +41 -0
  25. data/lib/forest_admin_agent/routes/resources/delete.rb +51 -0
  26. data/lib/forest_admin_agent/routes/resources/list.rb +38 -0
  27. data/lib/forest_admin_agent/routes/resources/related/associate_related.rb +63 -0
  28. data/lib/forest_admin_agent/routes/resources/related/count_related.rb +56 -0
  29. data/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +97 -0
  30. data/lib/forest_admin_agent/routes/resources/related/list_related.rb +54 -0
  31. data/lib/forest_admin_agent/routes/resources/related/update_related.rb +102 -0
  32. data/lib/forest_admin_agent/routes/resources/show.rb +44 -0
  33. data/lib/forest_admin_agent/routes/resources/store.rb +51 -0
  34. data/lib/forest_admin_agent/routes/resources/update.rb +42 -0
  35. data/lib/forest_admin_agent/routes/security/authentication.rb +95 -0
  36. data/lib/forest_admin_agent/routes/system/health_check.rb +22 -0
  37. data/lib/forest_admin_agent/serializer/forest_serializer.rb +176 -0
  38. data/lib/forest_admin_agent/serializer/forest_serializer_override.rb +103 -0
  39. data/lib/forest_admin_agent/services/ip_whitelist.rb +100 -0
  40. data/lib/forest_admin_agent/services/logger_service.rb +20 -0
  41. data/lib/forest_admin_agent/utils/condition_tree_parser.rb +57 -0
  42. data/lib/forest_admin_agent/utils/error_messages.rb +38 -0
  43. data/lib/forest_admin_agent/utils/id.rb +48 -0
  44. data/lib/forest_admin_agent/utils/query_string_parser.rb +89 -0
  45. data/lib/forest_admin_agent/utils/schema/frontend_filterable.rb +73 -0
  46. data/lib/forest_admin_agent/utils/schema/generator_collection.rb +35 -0
  47. data/lib/forest_admin_agent/utils/schema/generator_field.rb +183 -0
  48. data/lib/forest_admin_agent/utils/schema/schema_emitter.rb +103 -0
  49. data/lib/forest_admin_agent/version.rb +3 -0
  50. data/lib/forest_admin_agent.rb +11 -0
  51. data/sig/forest_admin_agent/auth/auth_manager.rbs +14 -0
  52. data/sig/forest_admin_agent/auth/oauth2/forest_provider.rbs +16 -0
  53. data/sig/forest_admin_agent/auth/oauth2/forest_resource_owner.rbs +15 -0
  54. data/sig/forest_admin_agent/auth/oidc_client_manager.rbs +15 -0
  55. data/sig/forest_admin_agent/builder/agent_factory.rbs +21 -0
  56. data/sig/forest_admin_agent/facades/container.rbs +9 -0
  57. data/sig/forest_admin_agent/http/router.rbs +9 -0
  58. data/sig/forest_admin_agent/routes/abstract_route.rbs +12 -0
  59. data/sig/forest_admin_agent/routes/security/authentication.rbs +14 -0
  60. data/sig/forest_admin_agent/routes/system/health_check.rbs +10 -0
  61. data/sig/forest_admin_agent.rbs +4 -0
  62. metadata +279 -0
@@ -0,0 +1,27 @@
1
+ module ForestAdminAgent
2
+ module Routes
3
+ class AbstractRoute
4
+ def initialize
5
+ @routes = {}
6
+ setup_routes
7
+ end
8
+
9
+ def build(args)
10
+ @datasource = ForestAdminAgent::Facades::Container.datasource
11
+ @collection = @datasource.collection(args[:params]['collection_name'])
12
+ end
13
+
14
+ def routes
15
+ @routes ||= {}
16
+ end
17
+
18
+ def add_route(name, method, uri, closure)
19
+ @routes[name] = { method: method, uri: uri, closure: closure }
20
+ end
21
+
22
+ def setup_routes
23
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ class Count < AbstractRoute
7
+ include ForestAdminAgent::Builder
8
+ def setup_routes
9
+ add_route('forest_count', 'get', '/:collection_name/count', ->(args) { handle_request(args) })
10
+
11
+ self
12
+ end
13
+
14
+ def handle_request(args = {})
15
+ build(args)
16
+
17
+ if @collection.is_countable?
18
+ caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
19
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new
20
+ aggregation = ForestAdminDatasourceToolkit::Components::Query::Aggregation.new(operation: 'Count')
21
+ result = @collection.aggregate(caller, filter, aggregation)
22
+
23
+ return {
24
+ name: args[:params]['collection_name'],
25
+ content: {
26
+ count: result[0][:value]
27
+ }
28
+ }
29
+ end
30
+
31
+ {
32
+ name: args[:params]['collection_name'],
33
+ content: {
34
+ count: 'deactivated'
35
+ }
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ require 'jsonapi-serializers'
2
+ require 'ostruct'
3
+
4
+ module ForestAdminAgent
5
+ module Routes
6
+ module Resources
7
+ class Delete < AbstractAuthenticatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Components::Query
10
+
11
+ def setup_routes
12
+ add_route('forest_delete_bulk', 'delete', '/:collection_name', ->(args) { handle_request_bulk(args) })
13
+ add_route('forest_delete', 'delete', '/:collection_name/:id', ->(args) { handle_request(args) })
14
+
15
+ self
16
+ end
17
+
18
+ def handle_request(args = {})
19
+ build(args)
20
+ id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
21
+ delete_records(args, { ids: [id], are_excluded: false })
22
+
23
+ { content: nil, status: 204 }
24
+ end
25
+
26
+ def handle_request_bulk(args = {})
27
+ build(args)
28
+ selection_ids = Utils::Id.parse_selection_ids(@collection, args[:params], with_key: true)
29
+ delete_records(args, selection_ids)
30
+
31
+ { content: nil, status: 204 }
32
+ end
33
+
34
+ def delete_records(args, selection_ids)
35
+ condition_tree_ids = ConditionTree::ConditionTreeFactory.match_records(@collection, selection_ids[:ids])
36
+ condition_tree_ids = condition_tree_ids.inverse if selection_ids[:are_excluded]
37
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
38
+ condition_tree: ConditionTree::ConditionTreeFactory.intersect(
39
+ [
40
+ Utils::QueryStringParser.parse_condition_tree(@collection, args),
41
+ condition_tree_ids
42
+ ]
43
+ )
44
+ )
45
+
46
+ @collection.delete(@caller, filter)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ class List < AbstractAuthenticatedRoute
7
+ include ForestAdminAgent::Builder
8
+ def setup_routes
9
+ add_route('forest_list', 'get', '/:collection_name', ->(args) { handle_request(args) })
10
+
11
+ self
12
+ end
13
+
14
+ def handle_request(args = {})
15
+ build(args)
16
+ caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
17
+
18
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
19
+ condition_tree: ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(@collection, args),
20
+ page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
21
+ )
22
+ projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@collection, args)
23
+ records = @collection.list(caller, filter, projection)
24
+
25
+ {
26
+ name: args[:params]['collection_name'],
27
+ content: JSONAPI::Serializer.serialize(
28
+ records,
29
+ is_collection: true,
30
+ serializer: Serializer::ForestSerializer,
31
+ include: projection.relations.keys
32
+ )
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,63 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ module Related
7
+ class AssociateRelated < AbstractRelatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Utils
10
+ include ForestAdminDatasourceToolkit::Components::Query
11
+ def setup_routes
12
+ add_route(
13
+ 'forest_related_associate',
14
+ 'post',
15
+ '/:collection_name/:id/relationships/:relation_name',
16
+ ->(args) { handle_request(args) }
17
+ )
18
+
19
+ self
20
+ end
21
+
22
+ def handle_request(args = {})
23
+ build(args)
24
+
25
+ parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
26
+ target_relation_id = Utils::Id.unpack_id(@child_collection, args[:params]['data'][0]['id'], with_key: true)
27
+ relation = Schema.get_to_many_relation(@collection, args[:params]['relation_name'])
28
+
29
+ if relation.type == 'OneToMany'
30
+ associate_one_to_many(relation, parent_id, target_relation_id)
31
+ else
32
+ associate_many_to_many(relation, parent_id, target_relation_id)
33
+ end
34
+
35
+ { content: nil, status: 204 }
36
+ end
37
+
38
+ private
39
+
40
+ def associate_one_to_many(relation, parent_id, target_relation_id)
41
+ id = Schema.primary_keys(@child_collection)[0]
42
+ value = Collection.get_value(@child_collection, @caller, target_relation_id, id)
43
+ filter = Filter.new(condition_tree: ConditionTree::Nodes::ConditionTreeLeaf.new(id, 'Equal', value))
44
+ value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)
45
+
46
+ @child_collection.update(@caller, filter, { relation.origin_key => value })
47
+ end
48
+
49
+ def associate_many_to_many(relation, parent_id, target_relation_id)
50
+ id = Schema.primary_keys(@child_collection)[0]
51
+ foreign_value = Collection.get_value(@child_collection, @caller, target_relation_id, id)
52
+ id = Schema.primary_keys(@collection)[0]
53
+ origin_value = Collection.get_value(@collection, @caller, parent_id, id)
54
+ record = { relation.origin_key => origin_value, relation.foreign_key => foreign_value }
55
+
56
+ through_collection = @datasource.collection(relation.through_collection)
57
+ through_collection.create(@caller, record)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,56 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ module Related
7
+ class CountRelated < AbstractRelatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Utils
10
+ include ForestAdminDatasourceToolkit::Components::Query
11
+ def setup_routes
12
+ add_route(
13
+ 'forest_related_count',
14
+ 'get',
15
+ '/:collection_name/:id/relationships/:relation_name/count',
16
+ ->(args) { handle_request(args) }
17
+ )
18
+
19
+ self
20
+ end
21
+
22
+ def handle_request(args = {})
23
+ build(args)
24
+
25
+ if @child_collection.is_countable?
26
+ filter = Filter.new
27
+ id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
28
+ result = Collection.aggregate_relation(
29
+ @collection,
30
+ id,
31
+ args[:params]['relation_name'],
32
+ @caller,
33
+ filter,
34
+ Aggregation.new(operation: 'Count')
35
+ )
36
+
37
+ return {
38
+ name: @child_collection.name,
39
+ content: {
40
+ count: result[0][:value]
41
+ }
42
+ }
43
+ end
44
+
45
+ {
46
+ name: @child_collection.name,
47
+ content: {
48
+ count: 'deactivated'
49
+ }
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,97 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ module Related
7
+ class DissociateRelated < AbstractRelatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Utils
10
+ include ForestAdminDatasourceToolkit::Components::Query
11
+ def setup_routes
12
+ add_route(
13
+ 'forest_related_dissociate',
14
+ 'delete',
15
+ '/:collection_name/:id/relationships/:relation_name',
16
+ ->(args) { handle_request(args) }
17
+ )
18
+
19
+ self
20
+ end
21
+
22
+ def handle_request(args = {})
23
+ build(args)
24
+
25
+ parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
26
+ is_delete_mode = !args.dig(:params, :delete).nil?
27
+ filter = get_base_foreign_filter(args)
28
+ relation = Schema.get_to_many_relation(@collection, args[:params]['relation_name'])
29
+
30
+ if relation.type == 'OneToMany'
31
+ dissociate_or_delete_one_to_many(relation, args[:params]['relation_name'], parent_id, is_delete_mode,
32
+ filter)
33
+ else
34
+ dissociate_or_delete_many_to_many(relation, args[:params]['relation_name'], parent_id, is_delete_mode,
35
+ filter)
36
+ end
37
+
38
+ { content: nil, status: 204 }
39
+ end
40
+
41
+ private
42
+
43
+ def dissociate_or_delete_one_to_many(relation, relation_name, parent_id, is_delete_mode, filter)
44
+ foreign_filter = FilterFactory.make_foreign_filter(@collection, parent_id, relation_name, @caller, filter)
45
+
46
+ if is_delete_mode
47
+ @child_collection.delete(@caller, foreign_filter)
48
+ else
49
+ @child_collection.update(@caller, foreign_filter, { relation.origin_key => nil })
50
+ end
51
+ end
52
+
53
+ def dissociate_or_delete_many_to_many(relation, relation_name, parent_id, is_delete_mode, filter)
54
+ through_collection = @datasource.collection(relation.through_collection)
55
+
56
+ if is_delete_mode
57
+ # Generate filters _BEFORE_ deleting stuff, otherwise things break.
58
+ foreign_filter = FilterFactory.make_foreign_filter(@collection, parent_id, relation_name, @caller, filter)
59
+ through_filter = FilterFactory.make_through_filter(@collection, parent_id, relation_name, @caller, filter)
60
+
61
+ # Delete records from through collection
62
+ through_collection.delete(@caller, through_filter)
63
+
64
+ # Let the datasource crash when:
65
+ # - the records in the foreignCollection are linked to other records in the origin collection
66
+ # - the underlying database/api is not cascading deletes
67
+ @child_collection.delete(@caller, foreign_filter)
68
+ else
69
+ through_filter = FilterFactory.make_through_filter(@collection, parent_id, relation_name, @caller, filter)
70
+ through_collection.delete(@caller, through_filter)
71
+ end
72
+ end
73
+
74
+ def get_base_foreign_filter(args)
75
+ selection_ids = Utils::Id.parse_selection_ids(@child_collection, args[:params])
76
+ selected_ids = ConditionTree::ConditionTreeFactory.match_ids(@child_collection, selection_ids[:ids])
77
+
78
+ selected_ids = selected_ids.inverse if selection_ids[:are_excluded]
79
+
80
+ if selection_ids[:ids].empty? && !selection_ids[:are_excluded]
81
+ raise ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Expected no empty id list'
82
+ end
83
+
84
+ Filter.new(
85
+ condition_tree: ConditionTree::ConditionTreeFactory.intersect(
86
+ [
87
+ Utils::QueryStringParser.parse_condition_tree(@child_collection, args),
88
+ selected_ids
89
+ ]
90
+ )
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,54 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ module Related
7
+ class ListRelated < AbstractRelatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Utils
10
+ def setup_routes
11
+ add_route(
12
+ 'forest_related_list',
13
+ 'get',
14
+ '/:collection_name/:id/relationships/:relation_name',
15
+ ->(args) { handle_request(args) }
16
+ )
17
+
18
+ self
19
+ end
20
+
21
+ def handle_request(args = {})
22
+ build(args)
23
+ # TODO: add csv behaviour
24
+
25
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
26
+ condition_tree: ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(@child_collection, args),
27
+ page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
28
+ )
29
+ projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@child_collection, args)
30
+ id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
31
+ records = Collection.list_relation(
32
+ @collection,
33
+ id,
34
+ args[:params]['relation_name'],
35
+ @caller,
36
+ filter,
37
+ projection
38
+ )
39
+
40
+ {
41
+ name: @child_collection.name,
42
+ content: JSONAPI::Serializer.serialize(
43
+ records,
44
+ is_collection: true,
45
+ serializer: Serializer::ForestSerializer,
46
+ include: projection.relations.keys
47
+ )
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,102 @@
1
+ require 'jsonapi-serializers'
2
+
3
+ module ForestAdminAgent
4
+ module Routes
5
+ module Resources
6
+ module Related
7
+ class UpdateRelated < AbstractRelatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Utils
10
+ include ForestAdminDatasourceToolkit::Components::Query
11
+ def setup_routes
12
+ add_route(
13
+ 'forest_related_update',
14
+ 'put',
15
+ '/:collection_name/:id/relationships/:relation_name',
16
+ ->(args) { handle_request(args) }
17
+ )
18
+
19
+ self
20
+ end
21
+
22
+ def handle_request(args = {})
23
+ build(args)
24
+
25
+ relation = @collection.fields[args[:params]['relation_name']]
26
+ parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'])
27
+
28
+ linked_id = if (id = args.dig(:params, :data, :id))
29
+ Utils::Id.unpack_id(@child_collection, id)
30
+ end
31
+
32
+ if relation.type == 'ManyToOne'
33
+ update_many_to_one(relation, parent_id, linked_id)
34
+ elsif relation.type == 'OneToOne'
35
+ update_one_to_one(relation, parent_id, linked_id)
36
+ end
37
+
38
+ { content: nil, status: 204 }
39
+ end
40
+
41
+ private
42
+
43
+ def update_many_to_one(relation, parent_id, linked_id)
44
+ foreign_value = if linked_id
45
+ Collection.get_value(@child_collection, @caller, linked_id, relation.foreign_key_target)
46
+ end
47
+ fk_owner = ConditionTree::ConditionTreeFactory.match_ids(@collection, [parent_id])
48
+ @collection.update(@caller, Filter.new(condition_tree: fk_owner), { relation.foreign_key => foreign_value })
49
+ end
50
+
51
+ def update_one_to_one(relation, parent_id, linked_id)
52
+ origin_value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)
53
+
54
+ break_old_one_to_one_relationship(nil, relation, origin_value, linked_id)
55
+ create_new_one_to_one_relationship(nil, relation, origin_value, linked_id)
56
+ end
57
+
58
+ def break_old_one_to_one_relationship(_scope, relation, origin_value, linked_id)
59
+ linked_id ||= []
60
+
61
+ old_fk_owner_filter = Filter.new(
62
+ condition_tree: ConditionTree::ConditionTreeFactory.intersect(
63
+ [
64
+ ConditionTree::Nodes::ConditionTreeLeaf.new(
65
+ relation.origin_key,
66
+ ConditionTree::Operators::EQUAL,
67
+ origin_value
68
+ )
69
+ ].push(
70
+ # Don't set the new record's field to null
71
+ # if it's already initialized with the right value
72
+ ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id]).inverse
73
+ )
74
+ )
75
+ )
76
+
77
+ result = @child_collection.aggregate(@caller, old_fk_owner_filter, Aggregation.new(operation: 'Count'), 1)
78
+
79
+ return unless (result[0][:value]).positive?
80
+
81
+ # Avoids updating records to null if it's not authorized by the ORM
82
+ # and if there is no record to update (the filter returns no record)
83
+
84
+ @child_collection.update(@caller, old_fk_owner_filter, { relation.origin_key => nil })
85
+ end
86
+
87
+ def create_new_one_to_one_relationship(_scope, relation, origin_value, linked_id)
88
+ return unless linked_id
89
+
90
+ new_fk_owner = ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id])
91
+
92
+ @child_collection.update(
93
+ @caller,
94
+ Filter.new(condition_tree: new_fk_owner),
95
+ { relation.origin_key => origin_value }
96
+ )
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,44 @@
1
+ require 'jsonapi-serializers'
2
+ require 'ostruct'
3
+
4
+ module ForestAdminAgent
5
+ module Routes
6
+ module Resources
7
+ class Show < AbstractAuthenticatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Components::Query
10
+ def setup_routes
11
+ add_route('forest_show', 'get', '/:collection_name/:id', ->(args) { handle_request(args) })
12
+
13
+ self
14
+ end
15
+
16
+ def handle_request(args = {})
17
+ build(args)
18
+ id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
19
+ caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
20
+ condition_tree = ConditionTree::ConditionTreeFactory.match_records(@collection, [id])
21
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
22
+ condition_tree: condition_tree,
23
+ page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
24
+ )
25
+ projection = ProjectionFactory.all(@collection)
26
+
27
+ records = @collection.list(caller, filter, projection)
28
+
29
+ raise Http::Exceptions::NotFoundError, 'Record does not exists' unless records.size.positive?
30
+
31
+ {
32
+ name: args[:params]['collection_name'],
33
+ content: JSONAPI::Serializer.serialize(
34
+ records[0],
35
+ is_collection: false,
36
+ serializer: Serializer::ForestSerializer,
37
+ include: projection.relations.keys
38
+ )
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'jsonapi-serializers'
2
+ require 'ostruct'
3
+
4
+ module ForestAdminAgent
5
+ module Routes
6
+ module Resources
7
+ class Store < AbstractAuthenticatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Components::Query
10
+ def setup_routes
11
+ add_route('forest_store', 'post', '/:collection_name', ->(args) { handle_request(args) })
12
+
13
+ self
14
+ end
15
+
16
+ def handle_request(args = {})
17
+ build(args)
18
+ data = format_attributes(args)
19
+ record = @collection.create(@caller, data)
20
+ link_one_to_one_relations(args, record)
21
+
22
+ {
23
+ name: args[:params]['collection_name'],
24
+ content: JSONAPI::Serializer.serialize(
25
+ record,
26
+ is_collection: false,
27
+ serializer: Serializer::ForestSerializer
28
+ )
29
+ }
30
+ end
31
+
32
+ def link_one_to_one_relations(args, record)
33
+ args[:params][:data][:relationships]&.map do |field, value|
34
+ schema = @collection.fields[field]
35
+ if schema.type == 'OneToOne'
36
+ id = Utils::Id.unpack_id(@collection, value['data']['id'], with_key: true)
37
+ foreign_collection = @datasource.collection(schema.foreign_collection)
38
+ # Load the value that will be used as origin_key
39
+ origin_value = record[schema.origin_key_target]
40
+
41
+ # update new relation (may update zero or one records).
42
+ condition_tree = ConditionTree::ConditionTreeFactory.match_records(foreign_collection, [id])
43
+ filter = Filter.new(condition_tree: condition_tree)
44
+ foreign_collection.update(@caller, filter, { schema.origin_key => origin_value })
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ require 'jsonapi-serializers'
2
+ require 'ostruct'
3
+
4
+ module ForestAdminAgent
5
+ module Routes
6
+ module Resources
7
+ class Update < AbstractAuthenticatedRoute
8
+ include ForestAdminAgent::Builder
9
+ include ForestAdminDatasourceToolkit::Components::Query
10
+
11
+ def setup_routes
12
+ add_route('forest_update', 'put', '/:collection_name/:id', ->(args) { handle_request(args) })
13
+
14
+ self
15
+ end
16
+
17
+ def handle_request(args = {})
18
+ build(args)
19
+ id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
20
+ caller = ForestAdminAgent::Utils::QueryStringParser.parse_caller(args)
21
+ condition_tree = ConditionTree::ConditionTreeFactory.match_records(@collection, [id])
22
+ filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
23
+ condition_tree: condition_tree,
24
+ page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
25
+ )
26
+ data = format_attributes(args)
27
+ @collection.update(@caller, filter, data)
28
+ records = @collection.list(caller, filter, ProjectionFactory.all(@collection))
29
+
30
+ {
31
+ name: args[:params]['collection_name'],
32
+ content: JSONAPI::Serializer.serialize(
33
+ records[0],
34
+ is_collection: false,
35
+ serializer: Serializer::ForestSerializer
36
+ )
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end