forest_liana 7.0.0.beta.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +20 -18
  3. data/app/controllers/forest_liana/application_controller.rb +0 -9
  4. data/app/controllers/forest_liana/associations_controller.rb +2 -2
  5. data/app/controllers/forest_liana/resources_controller.rb +15 -6
  6. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  7. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -3
  8. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  9. data/app/services/forest_liana/filters_parser.rb +8 -4
  10. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  11. data/app/services/forest_liana/has_many_getter.rb +2 -2
  12. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  13. data/app/services/forest_liana/line_stat_getter.rb +4 -2
  14. data/app/services/forest_liana/permissions_checker.rb +3 -4
  15. data/app/services/forest_liana/permissions_getter.rb +2 -2
  16. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  17. data/app/services/forest_liana/resource_getter.rb +6 -3
  18. data/app/services/forest_liana/resource_updater.rb +5 -2
  19. data/app/services/forest_liana/resources_getter.rb +6 -5
  20. data/app/services/forest_liana/scope_manager.rb +102 -0
  21. data/app/services/forest_liana/search_query_builder.rb +6 -3
  22. data/app/services/forest_liana/stat_getter.rb +2 -1
  23. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  24. data/config/routes.rb +3 -1
  25. data/lib/forest_liana/version.rb +1 -1
  26. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  27. data/spec/dummy/config/routes.rb +4 -0
  28. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  29. data/spec/requests/actions_controller_spec.rb +144 -23
  30. data/spec/requests/resources_spec.rb +2 -0
  31. data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
  32. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  33. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  34. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +1 -3
  35. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  36. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  37. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  38. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  39. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  40. metadata +20 -15
  41. data/app/services/forest_liana/scope_validator.rb +0 -98
  42. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  43. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  44. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  45. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  46. data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3115af32c59b5bf0e32218f3d40cb661809d58316714654520bde1bfee235098
4
- data.tar.gz: 850a6dbc96d8ff1e74ffe7970dc98ff2bd721d6b834087353052208722b55810
3
+ metadata.gz: 22a57de11c5ddee0b53cba1df6f74bebe5cb049534b7eabe58e8c77b5f7e8454
4
+ data.tar.gz: 3c26aeac71c9b1aab9e5fcd76434136e8a0779e88433f2522c1e39a18b44442c
5
5
  SHA512:
6
- metadata.gz: ea018906c77a41db7a7dc1022cd6485e5f9a293763c1822a64706092f8be74d1046230198af6d0041277cb8f2b852115633b96f622f69a192dc99f911cd31178
7
- data.tar.gz: cf12c7f0821b3e9232fe884e3d5c04470f6467d4a571ffc88ce2f6e36c9edac6704dcdd9a0c431dd222435bac1c31b512a5585bc9fb99f141e309e41f9ff8696
6
+ metadata.gz: ab71e9560ee8823ad87a3db7cb47ea142f020385dbe33b1679223b09f2190b98e30166864ac3765b58bbca1b8181036d5bd59dcfb616ef5b764a66d89f5d1138
7
+ data.tar.gz: 3d9c7dd145ec8616b85df2d7f11c27e65dc5311b5e4e3b03f427143f4a0a906f14cd1b797d840c4830147ace5866d2c5840afc377c36ebfd58412286ecc7e8dd
@@ -1,8 +1,13 @@
1
1
  module ForestLiana
2
- class ActionsController < ForestLiana::BaseController
2
+ class ActionsController < ApplicationController
3
3
 
4
- def values
5
- render serializer: nil, json: {}, status: :ok
4
+ def get_smart_action_hook_request
5
+ begin
6
+ params[:data][:attributes]
7
+ rescue => error
8
+ FOREST_LOGGER.error "Smart Action hook request error: #{error}"
9
+ {}
10
+ end
6
11
  end
7
12
 
8
13
  def get_collection(collection_name)
@@ -12,19 +17,12 @@ module ForestLiana
12
17
  def get_action(collection_name)
13
18
  collection = get_collection(collection_name)
14
19
  begin
15
- collection.actions.find {|action| ActiveSupport::Inflector.parameterize(action.name) == params[:action_name]}
20
+ collection.actions.find {|action| ActiveSupport::Inflector.parameterize(action.name) == params[:action_name]}
16
21
  rescue => error
17
22
  FOREST_LOGGER.error "Smart Action get action retrieval error: #{error}"
18
23
  nil
19
24
  end
20
25
  end
21
-
22
- def get_record
23
- model = ForestLiana::SchemaUtils.find_model_from_collection_name(params[:collectionName])
24
- redord_getter = ForestLiana::ResourceGetter.new(model, {:id => params[:recordIds][0]})
25
- redord_getter.perform
26
- redord_getter.record
27
- end
28
26
 
29
27
  def get_smart_action_load_ctx(fields)
30
28
  fields = fields.map do |field|
@@ -32,7 +30,7 @@ module ForestLiana
32
30
  field[:value] = nil unless field[:value]
33
31
  field
34
32
  end
35
- {:record => get_record, :fields => fields}
33
+ {:fields => fields, :params => params}
36
34
  end
37
35
 
38
36
  def get_smart_action_change_ctx(fields, field_changed)
@@ -42,7 +40,7 @@ module ForestLiana
42
40
  ForestLiana::WidgetsHelper.set_field_widget(field)
43
41
  field
44
42
  end
45
- {:record => get_record, :field_changed => found_field_changed, :fields => fields}
43
+ {:field_changed => found_field_changed, :fields => fields, :params => params}
46
44
  end
47
45
 
48
46
  def handle_result(result, action)
@@ -87,7 +85,9 @@ module ForestLiana
87
85
  end
88
86
 
89
87
  def load
90
- action = get_action(params[:collectionName])
88
+ load_request = get_smart_action_hook_request
89
+
90
+ action = get_action(load_request[:collection_name])
91
91
 
92
92
  if !action
93
93
  render status: 500, json: {error: 'Error in smart action load hook: cannot retrieve action from collection'}
@@ -103,18 +103,20 @@ module ForestLiana
103
103
  end
104
104
 
105
105
  def change
106
- action = get_action(params[:collectionName])
106
+ change_request = get_smart_action_hook_request
107
+
108
+ action = get_action(change_request[:collection_name])
107
109
 
108
110
  if !action
109
111
  return render status: 500, json: {error: 'Error in smart action change hook: cannot retrieve action from collection'}
110
- elsif params[:fields].nil?
112
+ elsif change_request[:fields].nil?
111
113
  return render status: 500, json: {error: 'Error in smart action change hook: fields params is mandatory'}
112
- elsif !params[:fields].is_a?(Array)
114
+ elsif !change_request[:fields].is_a?(Array)
113
115
  return render status: 500, json: {error: 'Error in smart action change hook: fields params must be an array'}
114
116
  end
115
117
 
116
118
  # Get the smart action hook change context
117
- context = get_smart_action_change_ctx(params[:fields], params[:changedField])
119
+ context = get_smart_action_change_ctx(change_request[:fields], change_request[:changed_field])
118
120
 
119
121
  field_changed_hook = context[:field_changed][:hook]
120
122
 
@@ -86,15 +86,6 @@ module ForestLiana
86
86
  end
87
87
  end
88
88
 
89
- def get_smart_action_context
90
- begin
91
- params[:data][:attributes].values[0].to_hash.symbolize_keys
92
- rescue => error
93
- FOREST_LOGGER.error "Smart Action context retrieval error: #{error}"
94
- {}
95
- end
96
- end
97
-
98
89
  def internal_server_error
99
90
  head :internal_server_error
100
91
  end
@@ -10,7 +10,7 @@ module ForestLiana
10
10
 
11
11
  def index
12
12
  begin
13
- getter = HasManyGetter.new(@resource, @association, params)
13
+ getter = HasManyGetter.new(@resource, @association, params, forest_user)
14
14
  getter.perform
15
15
 
16
16
  respond_to do |format|
@@ -25,7 +25,7 @@ module ForestLiana
25
25
 
26
26
  def count
27
27
  begin
28
- getter = HasManyGetter.new(@resource, @association, params)
28
+ getter = HasManyGetter.new(@resource, @association, params, forest_user)
29
29
  getter.count
30
30
 
31
31
  render serializer: nil, json: { count: getter.records_count }
@@ -29,7 +29,7 @@ module ForestLiana
29
29
  return head :forbidden unless checker.is_authorized?
30
30
  end
31
31
 
32
- getter = ForestLiana::ResourcesGetter.new(@resource, params)
32
+ getter = ForestLiana::ResourcesGetter.new(@resource, params, forest_user)
33
33
  getter.perform
34
34
 
35
35
  respond_to do |format|
@@ -63,7 +63,7 @@ module ForestLiana
63
63
  )
64
64
  return head :forbidden unless checker.is_authorized?
65
65
 
66
- getter = ForestLiana::ResourcesGetter.new(@resource, params)
66
+ getter = ForestLiana::ResourcesGetter.new(@resource, params, forest_user)
67
67
  getter.count
68
68
 
69
69
  render serializer: nil, json: { count: getter.records_count }
@@ -89,10 +89,12 @@ module ForestLiana
89
89
  checker = ForestLiana::PermissionsChecker.new(@resource, 'readEnabled', @rendering_id, user_id: forest_user['id'])
90
90
  return head :forbidden unless checker.is_authorized?
91
91
 
92
- getter = ForestLiana::ResourceGetter.new(@resource, params)
92
+ getter = ForestLiana::ResourceGetter.new(@resource, params, forest_user)
93
93
  getter.perform
94
94
 
95
95
  render serializer: nil, json: render_record_jsonapi(getter.record)
96
+ rescue ActiveRecord::RecordNotFound
97
+ render serializer: nil, json: { status: 404 }, status: :not_found
96
98
  rescue => error
97
99
  FOREST_LOGGER.error "Record Show error: #{error}\n#{format_stacktrace(error)}"
98
100
  internal_server_error
@@ -127,7 +129,7 @@ module ForestLiana
127
129
  checker = ForestLiana::PermissionsChecker.new(@resource, 'editEnabled', @rendering_id, user_id: forest_user['id'])
128
130
  return head :forbidden unless checker.is_authorized?
129
131
 
130
- updater = ForestLiana::ResourceUpdater.new(@resource, params)
132
+ updater = ForestLiana::ResourceUpdater.new(@resource, params, forest_user)
131
133
  updater.perform
132
134
 
133
135
  if updater.errors
@@ -149,7 +151,14 @@ module ForestLiana
149
151
  checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user_id: forest_user['id'])
150
152
  return head :forbidden unless checker.is_authorized?
151
153
 
152
- @resource.destroy(params[:id]) if @resource.exists?(params[:id])
154
+ collection_name = ForestLiana.name_for(@resource)
155
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(@resource, forest_user, collection_name, params[:timezone])
156
+
157
+ unless scoped_records.exists?(params[:id])
158
+ return render serializer: nil, json: { status: 404 }, status: :not_found
159
+ end
160
+
161
+ scoped_records.destroy(params[:id])
153
162
 
154
163
  head :no_content
155
164
  rescue => error
@@ -161,7 +170,7 @@ module ForestLiana
161
170
  checker = ForestLiana::PermissionsChecker.new(@resource, 'deleteEnabled', @rendering_id, user_id: forest_user['id'])
162
171
  return head :forbidden unless checker.is_authorized?
163
172
 
164
- ids = ForestLiana::ResourcesGetter.get_ids_from_request(params)
173
+ ids = ForestLiana::ResourcesGetter.get_ids_from_request(params, forest_user)
165
174
  @resource.destroy(ids) if ids&.any?
166
175
 
167
176
  head :no_content
@@ -0,0 +1,20 @@
1
+ module ForestLiana
2
+ class ScopesController < ForestLiana::ApplicationController
3
+ def invalidate_scope_cache
4
+ begin
5
+ rendering_id = params[:renderingId]
6
+
7
+ unless rendering_id
8
+ FOREST_LOGGER.error 'Missing renderingId'
9
+ return render serializer: nil, json: { status: 400 }, status: :bad_request
10
+ end
11
+
12
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
13
+ return render serializer: nil, json: { status: 200 }, status: :ok
14
+ rescue => error
15
+ FOREST_LOGGER.error "Error during scope cache invalidation: #{error.message}"
16
+ render serializer: nil, json: {status: 500 }, status: :internal_server_error
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,9 +1,9 @@
1
1
  module ForestLiana
2
2
  class SmartActionsController < ForestLiana::ApplicationController
3
3
  if Rails::VERSION::MAJOR < 4
4
- before_filter :check_permission_for_smart_route
4
+ before_filter :smart_action_pre_perform_checks
5
5
  else
6
- before_action :check_permission_for_smart_route
6
+ before_action :smart_action_pre_perform_checks
7
7
  end
8
8
 
9
9
  private
@@ -17,6 +17,41 @@ module ForestLiana
17
17
  end
18
18
  end
19
19
 
20
+ def smart_action_pre_perform_checks
21
+ check_permission_for_smart_route
22
+ ensure_record_ids_in_scope
23
+ end
24
+
25
+ def ensure_record_ids_in_scope
26
+ begin
27
+ attributes = get_smart_action_request
28
+
29
+ # if performing a `selectAll` let the `get_ids_from_request` handle the scopes
30
+ return if attributes[:all_records]
31
+
32
+ resource = find_resource(attributes[:collection_name])
33
+
34
+ # user is using the composite_primary_keys gem
35
+ if resource.primary_key.kind_of?(Array)
36
+ # TODO: handle primary keys
37
+ return
38
+ end
39
+
40
+ filter = JSON.generate({ 'field' => resource.primary_key, 'operator' => 'in', 'value' => attributes[:ids] })
41
+
42
+ resources_getter = ForestLiana::ResourcesGetter.new(resource, { :filters => filter, :timezone => attributes[:timezone] }, forest_user)
43
+
44
+ # resources getter will return records inside the scope. if the length differs then ids are out of scope
45
+ return if resources_getter.count == attributes[:ids].length
46
+
47
+ # target records are out of scope
48
+ render serializer: nil, json: { error: 'Smart Action: target record not found' }, status: :bad_request
49
+ rescue => error
50
+ FOREST_LOGGER.error "Smart Action: #{error}\n#{format_stacktrace(error)}"
51
+ render serializer: nil, json: { error: 'Smart Action: failed to evaluate permissions' }, status: :internal_server_error
52
+ end
53
+ end
54
+
20
55
  def check_permission_for_smart_route
21
56
  begin
22
57
 
@@ -58,7 +93,8 @@ module ForestLiana
58
93
  # smart action permissions are retrieved from the action's endpoint and http_method
59
94
  def get_smart_action_request_info
60
95
  {
61
- endpoint: request.fullpath,
96
+ # trim query params to get the endpoint
97
+ endpoint: request.fullpath.split('?').first,
62
98
  http_method: request.request_method
63
99
  }
64
100
  end
@@ -27,15 +27,15 @@ module ForestLiana
27
27
  def get
28
28
  case params[:type]
29
29
  when CHART_TYPE_VALUE
30
- stat = ValueStatGetter.new(@resource, params)
30
+ stat = ValueStatGetter.new(@resource, params, forest_user)
31
31
  when CHART_TYPE_PIE
32
- stat = PieStatGetter.new(@resource, params)
32
+ stat = PieStatGetter.new(@resource, params, forest_user)
33
33
  when CHART_TYPE_LINE
34
- stat = LineStatGetter.new(@resource, params)
34
+ stat = LineStatGetter.new(@resource, params, forest_user)
35
35
  when CHART_TYPE_OBJECTIVE
36
- stat = ObjectiveStatGetter.new(@resource, params)
36
+ stat = ObjectiveStatGetter.new(@resource, params, forest_user)
37
37
  when CHART_TYPE_LEADERBOARD
38
- stat = LeaderboardStatGetter.new(@resource, params)
38
+ stat = LeaderboardStatGetter.new(@resource, params, forest_user)
39
39
  end
40
40
 
41
41
  stat.perform
@@ -109,13 +109,15 @@ module ForestLiana
109
109
  parsed_value = parse_value(operator, value)
110
110
  field_and_operator = "#{parsed_field} #{parsed_operator}"
111
111
 
112
+ # parenthesis around the parsed_value are required to make the `IN` operator work
113
+ # and have no side effects on other requests
112
114
  if Rails::VERSION::MAJOR < 5
113
- "#{field_and_operator} #{ActiveRecord::Base.sanitize(parsed_value)}"
115
+ "#{field_and_operator} (#{ActiveRecord::Base.sanitize(parsed_value)})"
114
116
  # NOTICE: sanitize method as been removed in Rails 5.1 and sanitize_sql introduced in Rails 5.2.
115
117
  elsif Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR == 1
116
- "#{field_and_operator} #{ActiveRecord::Base.connection.quote(parsed_value)}"
118
+ "#{field_and_operator} (#{ActiveRecord::Base.connection.quote(parsed_value)})"
117
119
  else
118
- ActiveRecord::Base.sanitize_sql(["#{field_and_operator} ?", parsed_value])
120
+ ActiveRecord::Base.sanitize_sql(["#{field_and_operator} (?)", parsed_value])
119
121
  end
120
122
  end
121
123
 
@@ -147,6 +149,8 @@ module ForestLiana
147
149
  'IS'
148
150
  when 'present'
149
151
  'IS NOT'
152
+ when 'in'
153
+ 'IN'
150
154
  else
151
155
  raise_unknown_operator_error(operator)
152
156
  end
@@ -154,7 +158,7 @@ module ForestLiana
154
158
 
155
159
  def parse_value(operator, value)
156
160
  case operator
157
- when 'not', 'greater_than', 'less_than', 'not_equal', 'equal', 'before', 'after'
161
+ when 'not', 'greater_than', 'less_than', 'not_equal', 'equal', 'before', 'after', 'in'
158
162
  value
159
163
  when 'contains', 'not_contains'
160
164
  "%#{value}%"
@@ -1,5 +1,5 @@
1
1
  module ForestLiana
2
- class HasManyDissociator
2
+ class HasManyDissociator < ForestLiana::ApplicationController
3
3
  def initialize(resource, association, params)
4
4
  @resource = resource
5
5
  @association = association
@@ -17,7 +17,7 @@ module ForestLiana
17
17
  if @data.is_a?(Array)
18
18
  record_ids = @data.map { |record| record[:id] }
19
19
  elsif @data.dig('attributes').present?
20
- record_ids = ForestLiana::ResourcesGetter.get_ids_from_request(@params)
20
+ record_ids = ForestLiana::ResourcesGetter.get_ids_from_request(@params, forest_user)
21
21
  else
22
22
  record_ids = Array.new
23
23
  end
@@ -4,7 +4,7 @@ module ForestLiana
4
4
  attr_reader :includes
5
5
  attr_reader :records_count
6
6
 
7
- def initialize(resource, association, params)
7
+ def initialize(resource, association, params, forest_user)
8
8
  @resource = resource
9
9
  @association = association
10
10
  @params = params
@@ -13,7 +13,7 @@ module ForestLiana
13
13
  @collection = get_collection(@collection_name)
14
14
  compute_includes()
15
15
  includes_symbols = @includes.map { |include| include.to_sym }
16
- @search_query_builder = SearchQueryBuilder.new(@params, includes_symbols, @collection)
16
+ @search_query_builder = SearchQueryBuilder.new(@params, includes_symbols, @collection, forest_user)
17
17
 
18
18
  prepare_query()
19
19
  end
@@ -1,20 +1,22 @@
1
1
  module ForestLiana
2
2
  class LeaderboardStatGetter < StatGetter
3
- def initialize(resource, params)
4
- @resource = resource
5
- @params = params
6
- @model_relationship = @resource.reflect_on_association(@params[:relationship_field]).klass
7
- compute_includes()
8
- @label_field = @params[:label_field]
9
- @aggregate = @params[:aggregate].downcase
10
- @aggregate_field = @params[:aggregate_field]
11
- @limit = @params[:limit]
12
- @groub_by = "#{@resource.table_name}.#{@label_field}"
3
+ def initialize(parent_model, params, forest_user)
4
+ @scoped_parent_model = get_scoped_model(parent_model, forest_user, params[:timezone])
5
+ child_model = @scoped_parent_model.reflect_on_association(params[:relationship_field]).klass
6
+ @scoped_child_model = get_scoped_model(child_model, forest_user, params[:timezone])
7
+ @label_field = params[:label_field]
8
+ @aggregate = params[:aggregate].downcase
9
+ @aggregate_field = params[:aggregate_field]
10
+ @limit = params[:limit]
11
+ @groub_by = "#{@scoped_parent_model.table_name}.#{@label_field}"
13
12
  end
14
13
 
15
14
  def perform
16
- result = @model_relationship
17
- .joins(@includes)
15
+ includes = ForestLiana::QueryHelper.get_one_association_names_symbol(@scoped_child_model)
16
+
17
+ result = @scoped_child_model
18
+ .joins(includes)
19
+ .where({ @scoped_parent_model.name.downcase.to_sym => @scoped_parent_model })
18
20
  .group(@groub_by)
19
21
  .order(order)
20
22
  .limit(@limit)
@@ -24,8 +26,12 @@ module ForestLiana
24
26
  @record = Model::Stat.new(value: result)
25
27
  end
26
28
 
27
- def compute_includes
28
- @includes = ForestLiana::QueryHelper.get_one_association_names_symbol(@model_relationship)
29
+ def get_scoped_model(model, forest_user, timezone)
30
+ scope_filters = ForestLiana::ScopeManager.get_scope_for_user(forest_user, model.name, as_string: true)
31
+
32
+ return model.unscoped if scope_filters.blank?
33
+
34
+ FiltersParser.new(scope_filters, model, timezone).apply_filters
29
35
  end
30
36
 
31
37
  def order
@@ -25,8 +25,10 @@ module ForestLiana
25
25
  def perform
26
26
  value = get_resource().joins(@includes)
27
27
 
28
- unless @params[:filters].blank?
29
- value = FiltersParser.new(@params[:filters], value, @params[:timezone]).apply_filters
28
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @resource.name)
29
+
30
+ unless filters.blank?
31
+ value = FiltersParser.new(filters, @resource, @params[:timezone]).apply_filters
30
32
  end
31
33
 
32
34
  Groupdate.week_start = :monday