lanes 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/client/lanes/Config.coffee +3 -1
  4. data/client/lanes/access/screens/user-management/UserManagement.cjsx +2 -2
  5. data/client/lanes/components/grid/Body.cjsx +8 -1
  6. data/client/lanes/components/grid/EditingMixin.cjsx +5 -2
  7. data/client/lanes/components/grid/Grid.cjsx +1 -1
  8. data/client/lanes/components/grid/PopOverMixin.cjsx +1 -1
  9. data/client/lanes/components/grid/row-editor.scss +6 -0
  10. data/client/lanes/components/grid/styles.scss +1 -1
  11. data/client/lanes/components/record-finder/RecordFinder.cjsx +22 -16
  12. data/client/lanes/components/select-field/SelectField.cjsx +20 -27
  13. data/client/lanes/components/shared/DateTime.cjsx +12 -23
  14. data/client/lanes/components/shared/DisplayValue.cjsx +5 -10
  15. data/client/lanes/components/shared/FieldMixin.cjsx +107 -56
  16. data/client/lanes/components/shared/FieldWrapper.cjsx +59 -6
  17. data/client/lanes/components/shared/FormGroup.cjsx +3 -8
  18. data/client/lanes/components/shared/Helpers.coffee +1 -1
  19. data/client/lanes/components/shared/Icon.cjsx +1 -1
  20. data/client/lanes/components/shared/ImageAsset.cjsx +46 -0
  21. data/client/lanes/components/shared/Input.cjsx +5 -1
  22. data/client/lanes/components/shared/InputFieldMixin.cjsx +8 -27
  23. data/client/lanes/components/shared/NumberInput.cjsx +11 -4
  24. data/client/lanes/components/shared/RadioField.cjsx +18 -8
  25. data/client/lanes/components/shared/ScreenWrapper.cjsx +1 -1
  26. data/client/lanes/components/shared/TextArea.cjsx +15 -0
  27. data/client/lanes/components/shared/ToggleField.cjsx +11 -19
  28. data/client/lanes/components/shared/fields.scss +22 -76
  29. data/client/lanes/components/shared/{image-saver.scss → image-asset.scss} +1 -1
  30. data/client/lanes/components/shared/styles.scss +1 -1
  31. data/client/lanes/components/toolbar/RemoteChangeSets.cjsx +13 -10
  32. data/client/lanes/components/toolbar/changes-notification.scss +14 -1
  33. data/client/lanes/extension/Base.coffee +5 -1
  34. data/client/lanes/models/Asset.coffee +81 -0
  35. data/client/lanes/models/AssociationMap.coffee +14 -5
  36. data/client/lanes/models/AssociationProxy.coffee +9 -6
  37. data/client/lanes/models/Base.coffee +6 -3
  38. data/client/lanes/models/Collection.coffee +1 -3
  39. data/client/lanes/models/JobStatus.coffee +3 -0
  40. data/client/lanes/models/Query.coffee +3 -2
  41. data/client/lanes/models/Sync.coffee +6 -4
  42. data/client/lanes/react/mixins/Access.coffee +1 -1
  43. data/client/lanes/react/mixins/Data.coffee +33 -31
  44. data/client/lanes/screens/Commands.coffee +0 -1
  45. data/client/lanes/screens/Definitions.coffee +1 -1
  46. data/client/lanes/screens/SystemSettings.cjsx +23 -11
  47. data/client/lanes/screens/UserPreferences.cjsx +3 -2
  48. data/client/lanes/styles/bootstrap-custom-grid.scss +6 -4
  49. data/client/lanes/styles/fonts.scss +9 -0
  50. data/client/lanes/styles/global/styles.scss +1 -0
  51. data/client/lanes/styles/variables.scss +1 -1
  52. data/client/lanes/testing/Helpers.coffee +5 -3
  53. data/client/lanes/vendor/development/base.js +17206 -19211
  54. data/client/lanes/vendor/development/calendar.js +67 -471
  55. data/client/lanes/vendor/development/commons.js +21680 -19814
  56. data/client/lanes/vendor/development/helpers.js +40 -29
  57. data/client/lanes/vendor/development/toggle.js +22 -19
  58. data/client/lanes/vendor/development/widgets.js +2476 -2625
  59. data/client/lanes/vendor/production/base.js +19034 -21038
  60. data/client/lanes/vendor/production/calendar.js +67 -471
  61. data/client/lanes/vendor/production/commons.js +21369 -19136
  62. data/client/lanes/vendor/production/toggle.js +22 -19
  63. data/client/lanes/vendor/production/widgets.js +2476 -2625
  64. data/client/lanes/vendor/styles/widgets.scss +2 -0
  65. data/client/lanes/workspace/FirstRun.cjsx +69 -0
  66. data/client/lanes/workspace/Navbar.cjsx +4 -2
  67. data/client/lanes/workspace/ScreenView.cjsx +9 -1
  68. data/client/lanes/workspace/index.js +1 -0
  69. data/client/lanes/workspace/styles/screens.scss +11 -0
  70. data/config/database.yml +2 -2
  71. data/db/migrate/01_create_system_settings.rb +0 -1
  72. data/db/migrate/02_create_assets.rb +15 -0
  73. data/lanes.gemspec +25 -28
  74. data/lib/lanes/access/authentication_provider.rb +20 -7
  75. data/lib/lanes/access/role_collection.rb +9 -2
  76. data/lib/lanes/api/default_routes.rb +4 -4
  77. data/lib/lanes/api/formatted_reply.rb +15 -11
  78. data/lib/lanes/api/handlers/asset.rb +37 -0
  79. data/lib/lanes/api/request_wrapper.rb +18 -4
  80. data/lib/lanes/api/routing.rb +17 -6
  81. data/lib/lanes/api/updates.rb +1 -1
  82. data/lib/lanes/api.rb +1 -1
  83. data/lib/lanes/asset.rb +38 -0
  84. data/lib/lanes/concerns/all.rb +1 -1
  85. data/lib/lanes/concerns/asset_uploader.rb +60 -0
  86. data/lib/lanes/configuration.rb +1 -2
  87. data/lib/lanes/extension.rb +1 -1
  88. data/lib/lanes/logger.rb +26 -16
  89. data/lib/lanes/system_settings.rb +13 -8
  90. data/lib/lanes/version.rb +1 -1
  91. data/lib/lanes.rb +1 -0
  92. data/npm-build/base.js +1 -1
  93. data/npm-build/package.json +9 -9
  94. data/spec/command-reference-files/initial/Gemfile +1 -1
  95. data/spec/fixtures/logo.png +0 -0
  96. data/spec/lanes/components/grid/PopoverEditorSpec.coffee +48 -0
  97. data/spec/server/asset_spec.rb +34 -0
  98. data/spec/server/spec_helper.rb +14 -2
  99. metadata +118 -127
  100. data/client/lanes/components/shared/ControlLabel.cjsx +0 -45
  101. data/client/lanes/components/shared/ImageSaver.cjsx +0 -33
  102. data/client/lanes/models/SystemSettings.coffee +0 -0
  103. data/client/lanes/models/mixins/FileSupport.coffee +0 -60
  104. data/lib/lanes/api/handlers/file.rb +0 -26
  105. data/lib/lanes/concerns/image_uploader.rb +0 -42
@@ -561,6 +561,7 @@ ul.rw-list.rw-list-grouped > li.rw-list-option,
561
561
  cursor: not-allowed;
562
562
  }
563
563
  .rw-calendar-grid {
564
+ outline: none;
564
565
  height: 14.28571429em;
565
566
  table-layout: fixed;
566
567
  width: 100%;
@@ -602,6 +603,7 @@ ul.rw-list.rw-list-grouped > li.rw-list-option,
602
603
  position: relative;
603
604
  min-height: 27px;
604
605
  cursor: auto;
606
+ outline: none;
605
607
  padding-left: 5px;
606
608
  }
607
609
  .rw-selectlist > ul > li.rw-list-option > label > input {
@@ -0,0 +1,69 @@
1
+ class Lanes.Workspace.FirstRun extends Lanes.React.Screen
2
+
3
+ openSS: ->
4
+ Lanes.Screens.Definitions.all.get('system-settings').display()
5
+
6
+ openUP: ->
7
+ Lanes.Screens.Definitions.all.get('user-preferences').display()
8
+
9
+ openUser: ->
10
+ Lanes.Screens.Definitions.all.get('user-management').display()
11
+
12
+ render: ->
13
+ title = Lanes.Extensions.controlling().title()
14
+
15
+ <LC.ScreenWrapper identifier="first-run">
16
+
17
+ <BS.Row>
18
+ <BS.Col xs=10 xsOffset=1>
19
+ <BS.PageHeader>
20
+ Welcome to {title}
21
+ </BS.PageHeader>
22
+ </BS.Col>
23
+ </BS.Row>
24
+ <BS.Row>
25
+ <BS.Col xs=8 xsOffset=1>
26
+ <p className="lead">
27
+ A few tips & tricks:
28
+ </p>
29
+ </BS.Col>
30
+ </BS.Row>
31
+ <BS.Row>
32
+ <BS.Col xs=8 xsOffset=2>
33
+ <ul>
34
+ <li>
35
+ {title} is composed of individual screens. Each screen can be opened from the menu at the left.
36
+ When opened, each screen opens into a new tab in this window.
37
+ </li>
38
+ <li>
39
+ Many screens are dual-purpose and support both edit and display modes.
40
+ They can be switched between the modes by clicking
41
+ the <Lanes.Vendor.ReactToggle defaultChecked={true} /> control
42
+ at the top right of the screen.
43
+ </li>
44
+ <li>
45
+ You can setup the system by
46
+ selecting <LC.Icon type="cogs" /> <a onClick={@openSS}>System Settings</a>
47
+ from the menu option with
48
+ the <LC.Icon type="wrench" lg /> icon.
49
+ </li>
50
+ <li>
51
+ There’s also <LC.Icon type="user-secret" /> <a onClick={@openUP}>User Preferences</a> where
52
+ you can set which screens are displayed when {title} loads.
53
+ </li>
54
+ <li>
55
+ User accounts can be setup from
56
+ the <LC.Icon type='group' /> <a onClick={@openUser}>User Management</a> screen.
57
+ </li>
58
+ </ul>
59
+ </BS.Col>
60
+ </BS.Row>
61
+ <BS.Row className="help">
62
+ <BS.Col xs=7 xsOffset=4>
63
+ <p>Have a suggestion on improving {title}?</p>
64
+ <p><a href="https://argosity.com/" target="_blank">Argosity</a> would
65
+ love to hear it and help you.</p>
66
+ </BS.Col>
67
+ </BS.Row>
68
+
69
+ </LC.ScreenWrapper>
@@ -19,9 +19,11 @@ class Lanes.Workspace.Navbar extends Lanes.React.Component
19
19
 
20
20
  Logo: ->
21
21
  if @settings.logo?.url
22
- <img src={@settings.logo.thumb.url} />
22
+ <img src={@settings.logo.thumbnail_url} />
23
23
  else
24
- <span>Lanes</span>
24
+ <span>
25
+ {Lanes.Extensions.controlling().title()}
26
+ </span>
25
27
 
26
28
  render: ->
27
29
 
@@ -27,8 +27,16 @@ class Lanes.Workspace.ScreenView extends Lanes.React.Component
27
27
  screen = Lanes.Screens.Definitions.all.findWhere(loading: true)
28
28
  <LC.NetworkActivityOverlay visible model={screen} message="Loading #{screen.title}…" />
29
29
 
30
+ BaseView: ->
31
+ Base = Lanes.Extensions.controlling().initialScreen?() or Lanes.Workspace.FirstRun
32
+ <div className="screen active base">
33
+ <Base />
34
+ </div>
35
+
30
36
  render: ->
37
+
38
+ child = if @displaying.isEmpty() then <@BaseView /> else @displaying.map(@renderScreen)
31
39
  <div className={"page-content #{@context.uistate.layout_size}"}>
32
40
  {@renderLoading() if Lanes.Screens.Definitions.all.isLoading()}
33
- {@displaying.map @renderScreen}
41
+ {child}
34
42
  </div>
@@ -6,3 +6,4 @@
6
6
  //=require ./ScreenView
7
7
  //=require ./Navbar
8
8
  //=require ./Layout
9
+ //=require ./FirstRun
@@ -1,4 +1,15 @@
1
1
  .screen {
2
2
  padding: 5px;
3
3
  width: 100%;
4
+
5
+ > .first-run {
6
+ font-size: 1.3rem;
7
+ li + li {
8
+ margin-top: 1rem;
9
+ }
10
+ .help {
11
+ margin-top: 2rem;
12
+ p { text-align: right; }
13
+ }
14
+ }
4
15
  }
data/config/database.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  development:
2
2
  adapter: postgresql
3
- database: skr_dev
3
+ database: lanes_dev
4
4
  host: /tmp
5
5
 
6
6
  test:
7
7
  adapter: postgresql
8
- database: skr_dev
8
+ database: lanes_dev
9
9
  host: /tmp
@@ -2,7 +2,6 @@ class CreateSystemSettings < ActiveRecord::Migration
2
2
  def change
3
3
 
4
4
  create_table "system_settings" do |t|
5
- t.string "logo"
6
5
  t.jsonb "settings", null: false, default: {}
7
6
  end
8
7
 
@@ -0,0 +1,15 @@
1
+ class CreateAssets < ActiveRecord::Migration
2
+ def change
3
+
4
+ create_table "assets" do |t|
5
+ t.string :file, null: false
6
+
7
+ t.references :owner, polymorphic: true, null: false
8
+ t.integer :order
9
+ t.jsonb :metadata, null: false, default: {}
10
+ end
11
+
12
+ add_index :assets, [:owner_id, :owner_type]
13
+
14
+ end
15
+ end
data/lanes.gemspec CHANGED
@@ -24,45 +24,42 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = ">= 2.0"
26
26
 
27
- spec.add_dependency "activerecord", "~> 4.2"
28
27
  spec.add_dependency "activejob", "~> 4.2"
29
- spec.add_dependency "jobba", "~> 1.4"
30
-
31
- spec.add_dependency "resque", "~> 1.25"
32
- spec.add_dependency "pg", "~> 0.17"
33
- spec.add_dependency "sinatra", "~> 1.4"
28
+ spec.add_dependency "activerecord", "~> 4.2"
34
29
  spec.add_dependency "bcrypt", "~> 3.1"
35
- spec.add_dependency "oj", "~> 2.1"
36
- spec.add_dependency "message_bus", "2.0.0.beta.6"
37
- spec.add_dependency "rack-protection", "~> 1.5"
38
- spec.add_dependency "sprockets", "~> 3.0"
39
- spec.add_dependency "coffee-script", "~> 2.3"
40
- spec.add_dependency "coffee-react", "~> 3.0"
41
- spec.add_dependency "execjs", "~> 2.2"
42
- spec.add_dependency "thor", "~> 0.19"
43
- spec.add_dependency "sass", "~> 3.4"
44
- spec.add_dependency "sprockets-helpers", "~> 1.2"
30
+ spec.add_dependency "carrierwave", "~> 0.10.0"
31
+ spec.add_dependency "coffee-react", "~> 4.0"
32
+ spec.add_dependency "coffee-script", "~> 2.4"
45
33
  spec.add_dependency "compass-import-once", "~> 1.0"
46
- spec.add_dependency "guard", "~> 2.0"
34
+ spec.add_dependency "execjs", "~> 2.6"
35
+ spec.add_dependency "fastimage", "~> 1.8.1"
36
+ spec.add_dependency "guard", "~> 2.13"
37
+ spec.add_dependency "guard-jasmine", "~> 2.0"
38
+ spec.add_dependency "guard-minitest", "~> 2.3"
47
39
  spec.add_dependency "hashie", "~> 3.3"
48
- spec.add_dependency "rack-test", "~> 0.6"
40
+ spec.add_dependency "jasmine-core", "~> 2.0"
41
+ spec.add_dependency "jobba", "~> 1.4"
42
+ spec.add_dependency "message_bus", "2.0.0.beta.6"
43
+ spec.add_dependency "mini_magick", "~> 4.3.6"
49
44
  spec.add_dependency "minitest-around", "~> 0.2"
50
45
  spec.add_dependency "mocha", "~> 1.1"
51
- spec.add_dependency "guard-minitest", "~> 2.3"
52
-
53
- spec.add_dependency "uglifier", "~> 2.7"
54
- spec.add_dependency "guard-jasmine", "~> 2.0"
46
+ spec.add_dependency "oj", "~> 2.1"
47
+ spec.add_dependency "pg", "~> 0.8"
48
+ spec.add_dependency "rack-protection", "~> 1.5"
49
+ spec.add_dependency "rack-test", "~> 0.6"
55
50
  spec.add_dependency "rake", "~> 10.0"
56
- spec.add_dependency "jasmine-core", "~> 2.0"
57
51
  spec.add_dependency "require_all", "~> 1.3"
52
+ spec.add_dependency "resque", "~> 1.25"
58
53
  spec.add_dependency "sanitize", "~> 3.0"
59
- spec.add_dependency "carrierwave", "~> 0.10.0"
60
- spec.add_dependency "fog", "~> 1.37.0"
61
- spec.add_dependency "mini_magick", "~> 4.3.6"
62
- spec.add_dependency "fastimage", "~> 1.8.1"
54
+ spec.add_dependency "sass", "~> 3.4"
55
+ spec.add_dependency "sinatra", "~> 1.4"
56
+ spec.add_dependency "sprockets", "~> 3.6"
57
+ spec.add_dependency "sprockets-helpers", "~> 1.2"
58
+ spec.add_dependency "thor", "~> 0.19"
59
+ spec.add_dependency "uglifier", "~> 2.7"
63
60
 
64
61
  spec.add_development_dependency "bundler", "~> 1.5"
65
- spec.add_development_dependency "growl", "~> 1.0"
66
62
  spec.add_development_dependency "diffy", "~> 3.0"
63
+ spec.add_development_dependency "growl", "~> 1.0"
67
64
 
68
65
  end
@@ -13,7 +13,6 @@ module Lanes
13
13
  if Lanes.env.test? && request.env['HTTP_X_TESTING_USER'].present?
14
14
  Lanes::User.where(login: request.env['HTTP_X_TESTING_USER']).first
15
15
  else
16
- return Lanes::User.first
17
16
  Lanes::User.where(id: request.session['user_id']).first
18
17
  end
19
18
  )
@@ -47,19 +46,33 @@ module Lanes
47
46
  end
48
47
  end
49
48
 
50
- def wrap_reply(model, req)
49
+ def wrap_request(req)
50
+ if current_user
51
+ ::Lanes::User.scoped_to(current_user) do | user |
52
+ yield
53
+ end
54
+ else
55
+ fail_request(req)
56
+ end
57
+ end
58
+
59
+ def wrap_model_access(model, req)
51
60
  if allowed_access_to?(model)
52
61
  ::Lanes::User.scoped_to(current_user) do | user |
53
62
  yield
54
63
  end
55
64
  else
56
- Lanes.logger.warn request.env['HTTP_X_TESTING_USER']
57
- Lanes.logger.warn "Unauthorized access attempted to #{req.url}"
58
- req.halt( 401, Oj.dump({
59
- success:false, errors: {user: "Access Denied"}, message: "Access Denied"
60
- }))
65
+ fail_request(req)
61
66
  end
62
67
  end
68
+
69
+ def fail_request(req)
70
+ Lanes.logger.warn request.env['HTTP_X_TESTING_USER']
71
+ Lanes.logger.warn "Unauthorized access attempted to #{req.url}"
72
+ req.halt( 401, Oj.dump({
73
+ success:false, errors: {user: "Access Denied"}, message: "Access Denied"
74
+ }))
75
+ end
63
76
  end
64
77
 
65
78
  end
@@ -5,13 +5,20 @@ module Lanes
5
5
  include Enumerable
6
6
 
7
7
  def initialize(user)
8
- @roles ||= user.role_names.map{ |name|
8
+ @role_names = user.role_names
9
+ @roles = user.role_names.map{ |name|
9
10
  "Lanes::Access::Roles::#{name.classify}".safe_constantize
10
11
  }.compact.map{ |klass| klass.new(user) }
11
12
  end
12
13
 
13
14
  def exposed_data
14
- @roles.map{ |role| role.class.to_s.demodulize.downcase }
15
+ @role_names
16
+ end
17
+
18
+ # @param role [String]
19
+ # @return [Boolean] Does a role with the given id exist?
20
+ def include?(role)
21
+ @role_names.include?(role)
15
22
  end
16
23
 
17
24
  # @param model [Lanes::Model]
@@ -4,11 +4,11 @@ module Lanes
4
4
  put Lanes.config.api_path + '/system-settings.json',
5
5
  &SystemSettings.update_handler
6
6
 
7
- post Lanes.config.api_path + '/save-file-attribute',
8
- &API::Handlers::File.saver
7
+ post Lanes.config.api_path + '/asset',
8
+ &API::Handlers::Asset.saver
9
9
 
10
- get Lanes.config.api_path + '/file/*',
11
- &API::Handlers::File.getter
10
+ get Lanes.config.api_path + '/asset/*',
11
+ &API::Handlers::Asset.getter
12
12
 
13
13
  Extensions.each(reversed: true) do | ext |
14
14
  ext.route(self)
@@ -6,25 +6,29 @@ module Lanes
6
6
  # populates them appropriately
7
7
 
8
8
  def std_api_reply(type, data, options)
9
- success = options[:success].nil? ? true : options[:success]
10
- json = {}
11
- if data.is_a?(ActiveRecord::Base) && data.errors.any?
12
- json[:errors] = {}
13
- success = false
14
- data.errors.each{ | attr, message |
15
- json[:errors][attr] = message
16
- }
9
+ json = { success: options[:success].nil? ? true : options[:success] }
10
+ if data.is_a?(ActiveRecord::Base)
11
+ record_active_record_errors(data, json)
17
12
  end
18
13
  if options[:total_count]
19
14
  json[:total] = options.delete(:total_count)
20
15
  end
21
16
  json.merge(
22
- success: success,
23
- message: options[:messsage] || json_status_str(data, type.to_s.capitalize, success),
24
- data: success ? records_for_reply(data, type, options) : []
17
+ message: options[:messsage] || json_status_str(data, type.to_s.capitalize, json[:success]),
18
+ data: json[:success] ? records_for_reply(data, type, options) : []
25
19
  )
26
20
  end
27
21
 
22
+ def record_active_record_errors(model, json = {})
23
+ return if model.errors.none?
24
+ json[:success] = false
25
+ json[:errors] = {}
26
+ model.errors.each{ | attr, message |
27
+ json[:errors][attr] = message
28
+ }
29
+ json
30
+ end
31
+
28
32
  # @return Array<Array> returns either an array of fields
29
33
  def records_for_reply(data, type, options)
30
34
  return [] if :destroy == type
@@ -0,0 +1,37 @@
1
+ module Lanes::API::Handlers
2
+
3
+ class Asset
4
+
5
+ def self.saver
6
+ lambda do
7
+ Lanes.logger.debug "Saving File. Root=#{CarrierWave.root}"
8
+
9
+ path = "#{params['extension_id']}/#{params['owner_type']}"
10
+ model = path.underscore.camelize.constantize
11
+
12
+ authentication = Lanes::API::AuthenticationProvider.new(request)
13
+ authentication.wrap_model_access(model, self) do
14
+ owner = model.find(params['owner_id'])
15
+ asset = if params['id']
16
+ ::Lanes::Asset.find(params['id'])
17
+ else
18
+ owner.send("build_#{params['owner_association']}")
19
+ end
20
+ asset.store_uploaded_file(params['file'])
21
+
22
+ json_reply std_api_reply asset.new_record? ? :update : :create,
23
+ asset,
24
+ success: asset.save
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.getter
30
+ lambda do
31
+ # files are stored using a random string, therefore we assume that anyone who
32
+ # knows the filename has access and don't empose any further restrictions
33
+ send_file CarrierWave::Uploader::Base.root.call + '/' + params['splat'].first
34
+ end
35
+ end
36
+ end
37
+ end
@@ -28,20 +28,34 @@ module Lanes
28
28
  end
29
29
  end
30
30
 
31
- def make_handler(model, controller, parent_attribute)
31
+ def make_handler(model, controller, parent_attribute = nil)
32
32
  lambda do
33
33
  authentication = Lanes::API::AuthenticationProvider.new(request)
34
- authentication.wrap_reply(model, self) do
34
+ authentication.wrap_model_access(model, self) do
35
35
  if parent_attribute
36
36
  params[:nested_attribute] = Hash[ parent_attribute,
37
37
  params[parent_attribute] ]
38
38
  end
39
- wrap_reply(!request.get?) do
39
+ wrap_reply(with_transaction: !request.get?) do
40
40
  yield controller.new(model, authentication, params, data)
41
41
  end
42
42
  end
43
43
  end
44
44
  end
45
+
46
+ def with_authenticated_user(role:nil, with_transaction:true)
47
+ lambda do
48
+ authentication = Lanes::API::AuthenticationProvider.new(request)
49
+ user = authentication.current_user
50
+ if user and ( role.nil? or user.roles.include?(role) )
51
+ wrap_reply(with_transaction: with_transaction) do
52
+ yield authentication.current_user, self
53
+ end
54
+ else
55
+ authentication.fail_request(self)
56
+ end
57
+ end
58
+ end
45
59
  end
46
60
 
47
61
  def log_request
@@ -49,7 +63,7 @@ module Lanes
49
63
  Lanes.logger.debug JSON.pretty_generate(data) unless Lanes.env.production? or data.nil?
50
64
  end
51
65
 
52
- def wrap_reply(with_transaction=true)
66
+ def wrap_reply(with_transaction:true)
53
67
  response = { success: false, message: "No response was generated" }
54
68
  log_request
55
69
  if with_transaction
@@ -32,20 +32,31 @@ module Lanes
32
32
  prefix = parent_attribute ? parent_attribute + '/' : ''
33
33
 
34
34
  # index
35
- get "#{prefix}#{path}/?:id?.json", &RequestWrapper.get(model, controller, parent_attribute)
35
+ if controller.method_defined?(:perform_retrieval)
36
+ get "#{prefix}#{path}/?:id?.json",
37
+ &RequestWrapper.get(model, controller, parent_attribute)
38
+ end
36
39
 
37
40
  # create
38
- post "#{prefix}#{path}.json", &RequestWrapper.post(model, controller, parent_attribute)
41
+ if controller.method_defined?(:perform_creation)
42
+ post "#{prefix}#{path}.json",
43
+ &RequestWrapper.post(model, controller, parent_attribute)
44
+ end
39
45
 
40
46
  unless options[:immutable]
41
47
 
42
48
  # update
43
- patch "#{prefix}#{path}/?:id?.json", &RequestWrapper.update(model, controller, parent_attribute)
44
- put "#{prefix}#{path}/?:id?.json", &RequestWrapper.update(model, controller, parent_attribute)
49
+ if controller.method_defined?(:perform_update)
50
+ patch "#{prefix}#{path}/?:id?.json",
51
+ &RequestWrapper.update(model, controller, parent_attribute)
52
+ put "#{prefix}#{path}/?:id?.json",
53
+ &RequestWrapper.update(model, controller, parent_attribute)
54
+ end
45
55
 
46
- unless options[:indestructible]
56
+ if controller.method_defined?(:perform_destroy) and not options[:indestructible]
47
57
  # destroy
48
- delete "#{prefix}#{path}/?:id?.json", &RequestWrapper.delete(model, controller, parent_attribute)
58
+ delete "#{prefix}#{path}/?:id?.json",
59
+ &RequestWrapper.delete(model, controller, parent_attribute)
49
60
  end
50
61
 
51
62
  end
@@ -24,7 +24,7 @@ module Lanes
24
24
  Lanes::API::PubSub.publish(path, {
25
25
  by: self.user_info_for_change(model),
26
26
  update: model.changes
27
- })
27
+ })
28
28
  end
29
29
  end
30
30
 
data/lib/lanes/api.rb CHANGED
@@ -9,5 +9,5 @@ Lanes.config.get(:environment) do
9
9
  require_relative("api/test_specs") unless Lanes.env.production?
10
10
  end
11
11
 
12
- require_relative "api/handlers/file.rb"
12
+ require_relative "api/handlers/asset.rb"
13
13
  require_relative 'api/default_routes'
@@ -0,0 +1,38 @@
1
+ module Lanes
2
+
3
+ class Asset < Lanes::Model
4
+
5
+ mount_uploader :file, Lanes::Concerns::AssetUploader
6
+
7
+ belongs_to :owner, polymorphic: true
8
+
9
+ validates :owner, set: true
10
+
11
+ after_update :remove_changed_file, if: lambda{ file_changed? }
12
+
13
+ def serializable_hash(options = nil)
14
+ values = super
15
+ values.delete('file')
16
+ values.merge!(file.as_json[:file].stringify_keys)
17
+ values['original'] = { 'url' => values.delete('url') }
18
+ values
19
+ end
20
+
21
+ def store_uploaded_file(f)
22
+ ext = File.extname(f[:filename])
23
+ if ext.blank?
24
+ ext = '.' + FastImage.type(f[:tempfile]).to_s
25
+ end
26
+ self[:file] = ::Lanes::Strings.random + ext
27
+ file.store!(f)
28
+ end
29
+
30
+ protected
31
+
32
+ def remove_changed_file
33
+ self.file_was.remove!
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -10,5 +10,5 @@ require_relative "pub_sub"
10
10
  require_relative "code_identifier"
11
11
  require_relative "queries"
12
12
  require_relative "sanitize_fields"
13
- require_relative "image_uploader.rb"
14
13
  require_relative "sorting_expressions"
14
+ require_relative "asset_uploader"
@@ -0,0 +1,60 @@
1
+ require 'mini_magick'
2
+ require 'carrierwave'
3
+ require 'fastimage'
4
+
5
+ module Lanes::Concerns
6
+
7
+ class AssetUploader < CarrierWave::Uploader::Base
8
+
9
+ include CarrierWave::MiniMagick
10
+
11
+ process :store_attributes
12
+
13
+ version :medium, :if => :image? do
14
+ process :resize_to_fit => [800, 800]
15
+ end
16
+
17
+ version :thumbnail, :if => :image? do
18
+ process :resize_to_fit => [200,200]
19
+ end
20
+
21
+ def cache_dir
22
+ '/tmp'
23
+ end
24
+
25
+ def filename
26
+ if original_filename && model && model.read_attribute(mounted_as).present?
27
+ model.read_attribute(mounted_as)
28
+ end
29
+ end
30
+
31
+ def store_dir
32
+ token = secure_token
33
+ "#{token[0, 2]}/#{token[2, 2]}"
34
+ end
35
+
36
+ protected
37
+
38
+ def store_attributes
39
+ if file && model
40
+ model.metadata['size'] = file.size
41
+ model.metadata['content_type'] = file.content_type
42
+ if image?(file)
43
+ img = ::MiniMagick::Image.open(file.file)
44
+ model.metadata['width'] = img.width
45
+ model.metadata['height'] = img.height
46
+ end
47
+ end
48
+ end
49
+
50
+ def image?(new_file)
51
+ new_file.content_type.include? 'image'
52
+ end
53
+
54
+ def secure_token
55
+ model[mounted_as] ||= ::Lanes::Strings.random
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -4,7 +4,6 @@ require 'pathname'
4
4
  require 'carrierwave'
5
5
  require 'active_job'
6
6
  require 'jobba'
7
- require 'fog'
8
7
  require 'message_bus'
9
8
 
10
9
  module Lanes
@@ -106,7 +105,7 @@ module Lanes
106
105
 
107
106
  # Storage engine to use, default to file, may also be set to
108
107
  # 'fog' or any other value that CarrierWave accepts
109
- config_option :storage_type, 'file'
108
+ config_option :storage_type, :file
110
109
 
111
110
  def api_path
112
111
  mounted_at + 'api'