forest_admin_agent 1.0.0.pre.beta.21

Sign up to get free protection for your applications and to get access to all the features.
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