lanes 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/client/lanes/Config.coffee +26 -9
  3. data/client/lanes/access/screens/user-management/UserManagement.cjsx +1 -1
  4. data/client/lanes/components/grid/EditingMixin.cjsx +8 -4
  5. data/client/lanes/components/grid/PopOverMixin.cjsx +7 -1
  6. data/client/lanes/components/modal/Modal.cjsx +11 -1
  7. data/client/lanes/components/record-finder/RecordFinder.cjsx +8 -4
  8. data/client/lanes/components/shared/DateTime.cjsx +8 -6
  9. data/client/lanes/components/shared/FieldMixin.cjsx +3 -3
  10. data/client/lanes/components/shared/Icon.cjsx +1 -1
  11. data/client/lanes/components/shared/ImageAsset.cjsx +23 -11
  12. data/client/lanes/components/shared/NetworkActivityOverlay.cjsx +2 -0
  13. data/client/lanes/extension/Base.coffee +2 -0
  14. data/client/lanes/fonts/fontawesome-webfont.woff +0 -0
  15. data/client/lanes/fonts/fontawesome-webfont.woff2 +0 -0
  16. data/client/lanes/lib/RequestAssets.coffee +30 -0
  17. data/client/lanes/lib/all.js +1 -0
  18. data/client/lanes/lib/loader.js +93 -0
  19. data/client/lanes/lib/utilFunctions.coffee +12 -0
  20. data/client/lanes/models/Asset.coffee +3 -4
  21. data/client/lanes/models/AssociationMap.coffee +17 -6
  22. data/client/lanes/models/Base.coffee +20 -13
  23. data/client/lanes/models/Collection.coffee +4 -1
  24. data/client/lanes/models/PubSub.coffee +2 -3
  25. data/client/lanes/models/SmtpSettings.coffee +7 -0
  26. data/client/lanes/models/Sync.coffee +2 -2
  27. data/client/lanes/react/Viewport.coffee +14 -10
  28. data/client/lanes/react/mixins/FieldErrors.coffee +3 -4
  29. data/client/lanes/react/mixins/ReadEditingState.coffee +1 -0
  30. data/client/lanes/remote/Bootstrap.coffee +85 -0
  31. data/client/lanes/remote/api.coffee +2 -1
  32. data/client/lanes/remote/onDocumentReady.coffee +12 -0
  33. data/client/lanes/screens/Definitions.coffee +29 -12
  34. data/client/lanes/screens/SystemSettings.cjsx +12 -3
  35. data/client/lanes/styles/fonts/_bordered-pulled.scss +9 -0
  36. data/client/lanes/styles/fonts/_core.scss +1 -2
  37. data/client/lanes/styles/fonts/_icons.scss +56 -0
  38. data/client/lanes/styles/fonts/_mixins.scss +37 -4
  39. data/client/lanes/styles/fonts/_path.scss +2 -2
  40. data/client/lanes/styles/fonts/_screen-reader.scss +5 -0
  41. data/client/lanes/styles/fonts/_variables.scss +58 -2
  42. data/client/lanes/styles/fonts/font-awesome.scss +3 -1
  43. data/client/lanes/vendor/development/calendar.js +56 -57
  44. data/client/lanes/vendor/development/commons.js +31319 -29618
  45. data/client/lanes/vendor/development/data.js +8468 -7607
  46. data/client/lanes/vendor/development/helpers.js +265 -131
  47. data/client/lanes/vendor/development/toggle.js +288 -184
  48. data/client/lanes/vendor/development/ui.js +3387 -3492
  49. data/client/lanes/vendor/development/widgets.js +972 -1229
  50. data/client/lanes/vendor/production/calendar.js +60 -61
  51. data/client/lanes/vendor/production/commons.js +30695 -29032
  52. data/client/lanes/vendor/production/data.js +8457 -7598
  53. data/client/lanes/vendor/production/toggle.js +288 -184
  54. data/client/lanes/vendor/production/ui.js +3264 -3373
  55. data/client/lanes/vendor/production/widgets.js +972 -1229
  56. data/client/lanes/vendor/standalone/index.js +21106 -18761
  57. data/client/lanes/vendor/styles/toggle.scss +4 -3
  58. data/client/lanes/workspace/ScreenView.cjsx +2 -2
  59. data/client/lanes/workspace/styles/header.scss +4 -0
  60. data/config/routes.rb +0 -2
  61. data/db/migrate/01_create_system_settings.rb +1 -1
  62. data/db/migrate/02_create_assets.rb +1 -1
  63. data/lanes.gemspec +1 -0
  64. data/lib/lanes.rb +1 -0
  65. data/lib/lanes/access/track_modifications.rb +4 -2
  66. data/lib/lanes/api.rb +1 -0
  67. data/lib/lanes/api/cable.rb +11 -3
  68. data/lib/lanes/api/controller_base.rb +23 -19
  69. data/lib/lanes/api/default_routes.rb +9 -1
  70. data/lib/lanes/api/generic_controller.rb +1 -1
  71. data/lib/lanes/api/handlers/asset.rb +2 -3
  72. data/lib/lanes/api/helper_methods.rb +5 -11
  73. data/lib/lanes/api/pub_sub.rb +13 -7
  74. data/lib/lanes/api/request_wrapper.rb +1 -1
  75. data/lib/lanes/api/routing.rb +10 -7
  76. data/lib/lanes/api/to_json.rb +7 -0
  77. data/lib/lanes/asset.rb +4 -1
  78. data/lib/lanes/concerns/set_attribute_data.rb +2 -1
  79. data/lib/lanes/extension.rb +3 -1
  80. data/lib/lanes/mailer.rb +40 -0
  81. data/lib/lanes/rake_tasks.rb +4 -0
  82. data/lib/lanes/spec_helper.rb +11 -3
  83. data/lib/lanes/system_settings.rb +22 -9
  84. data/lib/lanes/version.rb +1 -1
  85. data/lib/lanes/workspace/extension.rb +5 -0
  86. data/npm-build/package.json +2 -2
  87. data/npm-build/react-toggle.js +1 -1
  88. data/npm-build/standalone.js +3 -0
  89. data/spec/command-reference-files/initial/Gemfile +1 -1
  90. data/spec/command-reference-files/initial/client/appy-app/Extension.coffee +2 -1
  91. data/spec/command-reference-files/model/db/migrate/20150218032025_create_test_tests.rb +1 -1
  92. data/spec/command-reference-files/screen/client/appy-app/Extension.coffee +2 -1
  93. data/spec/fixtures/system_settings.yml +8 -1
  94. data/spec/server/mailer_spec.rb +33 -0
  95. data/spec/server/system_settings_spec.rb +16 -0
  96. data/templates/client/Extension.coffee +2 -1
  97. data/templates/config/database.yml +1 -1
  98. data/templates/db/create_table_migration.rb +1 -1
  99. metadata +27 -6
  100. data/client/fonts/fontawesome-webfont.woff +0 -0
  101. data/client/fonts/fontawesome-webfont.woff2 +0 -0
  102. data/client/lanes/lib/loader.coffee +0 -100
  103. data/client/lanes/workspace/Modal.cjsx +0 -47
@@ -29,6 +29,7 @@
29
29
  }
30
30
 
31
31
  .react-toggle--disabled {
32
+ cursor: not-allowed;
32
33
  opacity: 0.5;
33
34
  -webkit-transition: opacity 0.25s;
34
35
  transition: opacity 0.25s;
@@ -45,7 +46,7 @@
45
46
  transition: all 0.2s ease;
46
47
  }
47
48
 
48
- .react-toggle:hover .react-toggle-track {
49
+ .react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
49
50
  background-color: #000000;
50
51
  }
51
52
 
@@ -53,7 +54,7 @@
53
54
  background-color: #19AB27;
54
55
  }
55
56
 
56
- .react-toggle.react-toggle--checked:hover .react-toggle-track {
57
+ .react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
57
58
  background-color: #128D15;
58
59
  }
59
60
 
@@ -131,7 +132,7 @@
131
132
  box-shadow: 0px 0px 2px 3px #0099E0;
132
133
  }
133
134
 
134
- .react-toggle:active .react-toggle-thumb {
135
+ .react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
135
136
  -webkit-box-shadow: 0px 0px 5px 5px #0099E0;
136
137
  -moz-box-shadow: 0px 0px 5px 5px #0099E0;
137
138
  box-shadow: 0px 0px 5px 5px #0099E0;
@@ -25,7 +25,7 @@ class Lanes.Workspace.ScreenView extends Lanes.React.Component
25
25
 
26
26
  renderLoading: ->
27
27
  screen = Lanes.Screens.Definitions.all.findWhere(loading: true)
28
- <LC.NetworkActivityOverlay visible model={screen} message="Loading #{screen.title}…" />
28
+ <LC.NetworkActivityOverlay visible message="Loading #{screen.title}…" />
29
29
 
30
30
  BaseView: ->
31
31
  Base = Lanes.Extensions.controlling().initialScreen?() or Lanes.Workspace.FirstRun
@@ -36,6 +36,6 @@ class Lanes.Workspace.ScreenView extends Lanes.React.Component
36
36
  render: ->
37
37
  child = if @displaying.isEmpty() then <@BaseView /> else @displaying.map(@renderScreen)
38
38
  <div className={"page-content #{@context.uistate.layout_size}"}>
39
- {@renderLoading() if Lanes.Screens.Definitions.all.isLoading()}
39
+ {@renderLoading() if @allScreens.isLoading()}
40
40
  {child}
41
41
  </div>
@@ -28,10 +28,14 @@
28
28
  text-shadow: rgb(143, 112, 112) 2px 2px 4px;
29
29
  padding: 0;
30
30
  overflow: hidden;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
31
34
  img {
32
35
  width: 100%;
33
36
  height: 100%;
34
37
  }
38
+
35
39
  }
36
40
 
37
41
  .navbar-toggle, .screens-menu-toggle {
@@ -1,7 +1,5 @@
1
1
  Lanes::API.routes.for_extension 'lanes' do
2
2
 
3
- root_view :lanes_root_view
4
-
5
3
  get 'job-statuses/:id.json' do
6
4
  wrap_reply do
7
5
  status = Lanes::Job.status_for_id(params[:id])
@@ -1,4 +1,4 @@
1
- class CreateSystemSettings < ActiveRecord::Migration
1
+ class CreateSystemSettings < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
 
4
4
  create_table "system_settings" do |t|
@@ -1,4 +1,4 @@
1
- class CreateAssets < ActiveRecord::Migration
1
+ class CreateAssets < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
 
4
4
  create_table "assets" do |t|
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "activejob", "~> 5.0"
28
28
  spec.add_dependency "activerecord", "~> 5.0"
29
29
  spec.add_dependency "actioncable", "~> 5.0"
30
+ spec.add_dependency "mail", "~> 2.6"
30
31
 
31
32
  spec.add_dependency "sinatra", "~> 2.0.0.beta2"
32
33
  spec.add_dependency "rack", "~> 2.0"
@@ -21,3 +21,4 @@ require_relative "lanes/extension"
21
21
  require_relative "lanes/screen"
22
22
  require_relative "lanes/job"
23
23
  require_relative "lanes/job/failure_logger"
24
+ require_relative "lanes/mailer"
@@ -5,7 +5,10 @@ module Lanes::Concerns
5
5
  # The class_name for the created_by and updated_by is set to {Lanes::Configuration#user_model}
6
6
  module TrackModifications
7
7
  extend ActiveSupport::Concern
8
-
8
+ ApiAttributeAccess::DEFAULT_BLACKLISTED.merge(
9
+ created_at: nil, updated_at: nil,
10
+ created_by_id: nil, updated_by_id: nil
11
+ )
9
12
  included do
10
13
  class_attribute :record_modifications, :instance_writer=>false
11
14
  self.record_modifications = true
@@ -13,7 +16,6 @@ module Lanes::Concerns
13
16
  belongs_to :created_by, :class_name=>Lanes::User
14
17
  belongs_to :updated_by, :class_name=>Lanes::User
15
18
 
16
- self.blacklist_attributes :created_at, :updated_at, :created_by_id, :updated_by_id
17
19
  before_update :record_update_modifications
18
20
  before_create :record_create_modifications
19
21
 
@@ -1,4 +1,5 @@
1
1
  require_relative '../lanes'
2
+ require_relative 'api/to_json'
2
3
  require_relative 'api/request_wrapper'
3
4
  require_relative 'api/error_formatter'
4
5
  require_relative 'api/formatted_reply'
@@ -7,6 +7,9 @@ module Lanes
7
7
  mattr_reader :server
8
8
  mattr_reader :config
9
9
 
10
+ def self.handle_request(request)
11
+ @@server.call(request.env)
12
+ end
10
13
 
11
14
  class Channel < ActionCable::Channel::Base
12
15
  end
@@ -15,9 +18,13 @@ module Lanes
15
18
  identified_by :current_user
16
19
 
17
20
  def connect
18
- reject_unauthorized_connection unless
19
- cookies['user_id'] &&
20
- self.current_user = Lanes::User.where(id: cookies['user_id']).first
21
+ unless cookies['user_id'] &&
22
+ self.current_user = Lanes::User
23
+ .where(id: cookies['user_id']).first
24
+ Lanes.logger.warn("Rejecting ws connection due to unauthorized access by user_id #{cookies['user_id']}")
25
+
26
+ reject_unauthorized_connection
27
+ end
21
28
  end
22
29
 
23
30
  protected
@@ -28,6 +35,7 @@ module Lanes
28
35
  end
29
36
 
30
37
  def self.configure
38
+
31
39
  require_relative 'updates'
32
40
  @@config = ActionCable::Server::Configuration.new
33
41
  config.logger = Lanes.logger
@@ -21,18 +21,22 @@ module Lanes
21
21
 
22
22
  class ControllerBase
23
23
 
24
- attr_reader :model, :user, :params, :data
24
+ attr_reader :model, :params, :data
25
25
  include FormattedReply
26
26
 
27
27
  def initialize(model, authentication, params, data={})
28
- @user = authentication.current_user
29
28
  @model = model
30
29
  @params = params
31
30
  @data = data
31
+ @authentication = authentication
32
32
  end
33
33
 
34
34
  protected
35
35
 
36
+ def current_user
37
+ @current_user ||= @authentication.current_user
38
+ end
39
+
36
40
  def perform_retrieval
37
41
  query = build_query
38
42
  query = add_scopes_to_query(query)
@@ -61,7 +65,7 @@ module Lanes
61
65
  query = add_access_limits_to_query(query)
62
66
  success = true
63
67
  query.each do | record |
64
- if user.can_delete?(record, record.id)
68
+ if current_user.can_delete?(record, record.id)
65
69
  success = false unless record.destroy
66
70
  end
67
71
  end
@@ -78,8 +82,8 @@ module Lanes
78
82
  query.each do | record |
79
83
  record_data = data.detect{ |rd| rd['id'] == record.id }
80
84
  next unless record_data
81
- if user.can_write?(record, record.id)
82
- record.set_attribute_data(record_data, user)
85
+ if current_user.can_write?(record, record.id)
86
+ record.set_attribute_data(record_data, current_user)
83
87
  success = false unless record.save
84
88
  end
85
89
  end
@@ -91,7 +95,7 @@ module Lanes
91
95
  query = build_query
92
96
  query = add_access_limits_to_query(query)
93
97
  record = query.first!
94
- record.set_attribute_data(data, user)
98
+ record.set_attribute_data(data, current_user)
95
99
  options = build_reply_options.merge(success: record.save)
96
100
  std_api_reply(:update, record, options)
97
101
  end
@@ -135,25 +139,25 @@ module Lanes
135
139
  options = {}
136
140
  if include_associations.any?
137
141
  options[:include] = include_associations.each_with_object({}) do |association, includes|
138
- includes.merge! build_allowed_associations(association, user)
142
+ includes.merge! build_allowed_associations(association)
139
143
  end
140
144
  end
141
145
 
142
146
  if requested_fields.any?
143
- options[:methods] = requested_fields.select{|f| model.has_exported_method?(f,user) }
147
+ options[:methods] = requested_fields.select{|f| model.has_exported_method?(f, current_user) }
144
148
  end
145
149
  options[:format] = reply_with_array? ? 'array' : 'object'
146
150
  options
147
151
  end
148
152
 
149
- def build_allowed_associations(association, user, model_class=self.model)
153
+ def build_allowed_associations(association, model_class = self.model)
150
154
  includes = {}
151
155
  if association.is_a?(Hash)
152
156
  association.each do |include_name, sub_associations|
153
- if model_class.has_exported_association?(include_name, user) &&
157
+ if model_class.has_exported_association?(include_name, current_user) &&
154
158
  ( reflection = model_class.reflect_on_association( include_name.to_sym ) )
155
159
  sub_includes = includes[include_name.to_sym] = {}
156
- allowed = build_allowed_associations( sub_associations, user, reflection.klass )
160
+ allowed = build_allowed_associations( sub_associations, reflection.klass )
157
161
  unless allowed.empty?
158
162
  sub_includes[:include] ||= []
159
163
  sub_includes[:include] << allowed
@@ -162,12 +166,12 @@ module Lanes
162
166
  end
163
167
  elsif association.is_a?(Array)
164
168
  association.each do | sub_association |
165
- if model_class.has_exported_association?(sub_association, user)
166
- includes.merge! build_allowed_associations( sub_association, user, model_class )
169
+ if model_class.has_exported_association?(sub_association, current_user)
170
+ includes.merge! build_allowed_associations( sub_association, model_class )
167
171
  end
168
172
  end
169
173
  else
170
- includes[ association.to_sym ] = {} if model_class.has_exported_association?(association,user)
174
+ includes[ association.to_sym ] = {} if model_class.has_exported_association?(association, current_user)
171
175
  end
172
176
  includes
173
177
  end
@@ -176,7 +180,7 @@ module Lanes
176
180
 
177
181
  def add_access_limits_to_query(query)
178
182
  if model.respond_to?(:access_limits_for_query)
179
- query = model.access_limits_for_query(query, user, params)
183
+ query = model.access_limits_for_query(query, current_user, params)
180
184
  else
181
185
  query
182
186
  end
@@ -216,11 +220,11 @@ module Lanes
216
220
  if desired.is_a?(Hash)
217
221
  nested = {}
218
222
  desired.each do | name, sub_associations |
219
- nested[name.to_sym] = sub_associations if model.has_exported_association?(name,user)
223
+ nested[name.to_sym] = sub_associations if model.has_exported_association?(name, current_user)
220
224
  end
221
225
  results.push(nested) unless nested.empty?
222
226
  else
223
- results.push(desired.to_sym) if model.has_exported_association?(desired,user)
227
+ results.push(desired.to_sym) if model.has_exported_association?(desired, current_user)
224
228
  end
225
229
  end
226
230
  query = query.includes(allowed_includes) unless allowed_includes.empty?
@@ -242,7 +246,7 @@ module Lanes
242
246
 
243
247
  def add_scope_to_query(query)
244
248
  query_scopes.each do | name, arg |
245
- if model.has_exported_scope?(name,user)
249
+ if model.has_exported_scope?(name, current_user)
246
250
  args = [name]
247
251
  args.push( arg ) unless arg.blank?
248
252
  query = query.send( *args )
@@ -268,7 +272,7 @@ module Lanes
268
272
  def convert_field_to_arel(field)
269
273
  if field.include?('.')
270
274
  (table_name, field_name) = field.split('.')
271
- if model.has_exported_join_table?(table_name, user)
275
+ if model.has_exported_join_table?(table_name, current_user)
272
276
  Arel::Table.new(table_name)[field_name]
273
277
  else
274
278
  nil
@@ -1,10 +1,14 @@
1
1
  require_relative "handlers/asset.rb"
2
2
 
3
+ unless Lanes.env.production?
4
+ require_relative("test_specs")
5
+ end
6
+
3
7
  module Lanes
4
8
  API.routes.draw do
5
9
  # WS endpoint must come first
6
10
  get Lanes.config.api_path + '/ws' do
7
- API::Cable.server.call(env)
11
+ API::Cable.handle_request(request)
8
12
  end
9
13
 
10
14
  Extensions.each(reversed: true) do | ext |
@@ -24,5 +28,9 @@ module Lanes
24
28
  API::PubSub.publish("file-change", data)
25
29
  "OK"
26
30
  end
31
+
27
32
  end
33
+
34
+ API.set_root_view :lanes_root_view
35
+
28
36
  end
@@ -9,7 +9,7 @@ module Lanes
9
9
  end
10
10
 
11
11
  def create
12
- record = model.from_attribute_data(data, user)
12
+ record = model.from_attribute_data(data, current_user)
13
13
  options = build_reply_options.merge(success: record.save)
14
14
  std_api_reply(:create, record, options)
15
15
  end
@@ -34,10 +34,9 @@ module Lanes::API::Handlers
34
34
  end
35
35
 
36
36
  def self.getter
37
- root = Lanes::Extensions.controlling
38
- .root_path.join('public', 'files')
37
+ root = Lanes::Extensions.controlling.root_path.join('public', 'files')
39
38
  lambda do
40
- - send_file(root.join( params['splat'].first ).to_s)
39
+ send_file(root.join( params['splat'].first ).to_s)
41
40
  end
42
41
  end
43
42
  end
@@ -1,8 +1,6 @@
1
1
  module Lanes
2
2
  module API
3
-
4
3
  module HelperMethods
5
-
6
4
  def lanes_application_title
7
5
  Extensions.controlling.title
8
6
  end
@@ -16,8 +14,7 @@ module Lanes
16
14
  end
17
15
 
18
16
  def client_bootstrap_data(mergedWith: {})
19
- Oj.dump(Extensions.client_bootstrap_data(self)
20
- .merge(mergedWith), mode: :compat)
17
+ API.to_json(Extensions.client_bootstrap_data(self).merge(mergedWith))
21
18
  end
22
19
 
23
20
  def csrf_token
@@ -30,11 +27,11 @@ module Lanes
30
27
 
31
28
  def error_as_json
32
29
  Lanes.logger.warn request.env['sinatra.error']
33
- Oj.dump({
30
+ API.to_json(
34
31
  success: false,
35
32
  errors: { exception: request.env['sinatra.error'].message },
36
33
  message: request.env['sinatra.error'].message
37
- }, mode: :compat)
34
+ )
38
35
  end
39
36
 
40
37
  def data
@@ -45,13 +42,10 @@ module Lanes
45
42
  @request_origin ||= env['HTTP_ORIGIN']
46
43
  end
47
44
 
48
- def json_reply( response )
45
+ def json_reply(response)
49
46
  content_type 'application/json'
50
- Oj.dump(response, mode: :compat)
47
+ API.to_json(response)
51
48
  end
52
-
53
-
54
49
  end
55
-
56
50
  end
57
51
  end
@@ -5,23 +5,29 @@ module Lanes
5
5
 
6
6
  class PubSub < Cable::Channel
7
7
  PREFIX = 'ps:'
8
+
8
9
  def on(data)
9
- channel = PREFIX + data['channel']
10
- stream_from channel
10
+ stream_from channel_prefix + data['channel']
11
11
  end
12
12
 
13
13
  def off(data)
14
- channel = PREFIX + data['channel']
14
+ channel = channel_prefix + data['channel']
15
15
  cb = pubsub.instance_variable_get('@listener')
16
16
  .instance_variable_get('@subscribers')[channel].first
17
17
  pubsub.unsubscribe(channel, cb)
18
18
  end
19
19
 
20
20
  def self.publish(channel, data)
21
- ActionCable.server.broadcast(
22
- PREFIX + channel,
23
- data.merge(channel: channel)
24
- )
21
+ channel = channel_prefix + channel
22
+ ActionCable.server.broadcast(channel, data.merge(channel: channel))
23
+ end
24
+
25
+ def self.channel_prefix
26
+ PREFIX
27
+ end
28
+
29
+ def channel_prefix
30
+ PREFIX
25
31
  end
26
32
 
27
33
  end
@@ -37,7 +37,7 @@ module Lanes
37
37
  params[:nested_attribute] = Hash[ options[:parent_attribute],
38
38
  params[parent_attribute] ]
39
39
  end
40
- wrap_reply(options.merge(with_transaction: !request.get?)) do
40
+ wrap_reply(options.reverse_merge(with_transaction: !request.get?)) do
41
41
  yield controller.new(model, authentication, params, data)
42
42
  end
43
43
  end