padrino-admin 0.16.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +3 -3
  3. data/Rakefile +0 -21
  4. data/lib/padrino-admin/access_control.rb +17 -4
  5. data/lib/padrino-admin/generators/actions.rb +1 -1
  6. data/lib/padrino-admin/generators/admin_app.rb +9 -11
  7. data/lib/padrino-admin/generators/admin_page.rb +12 -2
  8. data/lib/padrino-admin/generators/orm.rb +30 -47
  9. data/lib/padrino-admin/generators/templates/account/seeds.rb.tt +7 -2
  10. data/lib/padrino-admin/generators/templates/app/controllers/sessions.rb.tt +0 -4
  11. data/lib/padrino-admin/generators/templates/app.rb.tt +4 -1
  12. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/affix.js +63 -36
  13. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/alert.js +18 -11
  14. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/bootstrap.min.js +4 -4
  15. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/button.js +41 -23
  16. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/carousel.js +90 -49
  17. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/collapse.js +86 -44
  18. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/dropdown.js +66 -48
  19. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/modal.js +167 -52
  20. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/popover.js +33 -20
  21. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/scrollspy.js +58 -39
  22. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tab.js +55 -25
  23. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tooltip.js +370 -92
  24. data/lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/transition.js +22 -11
  25. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-3.7.1.min.js +2 -0
  26. data/lib/padrino-admin/generators/templates/assets/stylesheets/application.css +14 -0
  27. data/lib/padrino-admin/generators/templates/assets/stylesheets/bootstrap.css +6 -4
  28. data/lib/padrino-admin/generators/templates/erb/app/base/index.erb.tt +1 -1
  29. data/lib/padrino-admin/generators/templates/erb/app/layouts/application.erb.tt +1 -1
  30. data/lib/padrino-admin/generators/templates/erb/app/sessions/new.erb.tt +1 -1
  31. data/lib/padrino-admin/generators/templates/erb/page/index.erb.tt +2 -2
  32. data/lib/padrino-admin/generators/templates/haml/app/base/index.haml.tt +1 -1
  33. data/lib/padrino-admin/generators/templates/haml/app/layouts/application.haml.tt +1 -1
  34. data/lib/padrino-admin/generators/templates/haml/app/sessions/new.haml.tt +1 -1
  35. data/lib/padrino-admin/generators/templates/slim/app/base/index.slim.tt +1 -1
  36. data/lib/padrino-admin/generators/templates/slim/app/layouts/application.slim.tt +1 -1
  37. data/lib/padrino-admin/generators/templates/slim/app/sessions/new.slim.tt +1 -1
  38. data/lib/padrino-admin/generators/templates/slim/page/index.slim.tt +2 -2
  39. data/lib/padrino-admin/helpers/authentication_helpers.rb +1 -1
  40. data/lib/padrino-admin/helpers/view_helpers.rb +3 -3
  41. data/lib/padrino-admin/locale/orm/pl.yml +0 -11
  42. data/lib/padrino-admin.rb +1 -1
  43. data/padrino-admin.gemspec +0 -2
  44. data/test/generators/test_account_model_generator.rb +1 -60
  45. data/test/generators/test_admin_app_generator.rb +159 -25
  46. data/test/generators/test_admin_page_generator.rb +110 -9
  47. data/test/test_admin_application.rb +123 -0
  48. metadata +9 -124
  49. data/lib/padrino-admin/bootstrap-less/alerts.less +0 -67
  50. data/lib/padrino-admin/bootstrap-less/badges.less +0 -55
  51. data/lib/padrino-admin/bootstrap-less/bootstrap.less +0 -49
  52. data/lib/padrino-admin/bootstrap-less/breadcrumbs.less +0 -26
  53. data/lib/padrino-admin/bootstrap-less/button-groups.less +0 -226
  54. data/lib/padrino-admin/bootstrap-less/buttons.less +0 -155
  55. data/lib/padrino-admin/bootstrap-less/carousel.less +0 -232
  56. data/lib/padrino-admin/bootstrap-less/close.less +0 -33
  57. data/lib/padrino-admin/bootstrap-less/code.less +0 -63
  58. data/lib/padrino-admin/bootstrap-less/component-animations.less +0 -29
  59. data/lib/padrino-admin/bootstrap-less/dropdowns.less +0 -213
  60. data/lib/padrino-admin/bootstrap-less/font-awesome/bordered-pulled.less +0 -16
  61. data/lib/padrino-admin/bootstrap-less/font-awesome/core.less +0 -12
  62. data/lib/padrino-admin/bootstrap-less/font-awesome/fixed-width.less +0 -6
  63. data/lib/padrino-admin/bootstrap-less/font-awesome/font-awesome.less +0 -17
  64. data/lib/padrino-admin/bootstrap-less/font-awesome/icons.less +0 -412
  65. data/lib/padrino-admin/bootstrap-less/font-awesome/larger.less +0 -13
  66. data/lib/padrino-admin/bootstrap-less/font-awesome/list.less +0 -19
  67. data/lib/padrino-admin/bootstrap-less/font-awesome/mixins.less +0 -20
  68. data/lib/padrino-admin/bootstrap-less/font-awesome/path.less +0 -14
  69. data/lib/padrino-admin/bootstrap-less/font-awesome/rotated-flipped.less +0 -9
  70. data/lib/padrino-admin/bootstrap-less/font-awesome/spinning.less +0 -30
  71. data/lib/padrino-admin/bootstrap-less/font-awesome/stacked.less +0 -20
  72. data/lib/padrino-admin/bootstrap-less/font-awesome/variables.less +0 -381
  73. data/lib/padrino-admin/bootstrap-less/forms.less +0 -419
  74. data/lib/padrino-admin/bootstrap-less/glyphicons.less +0 -233
  75. data/lib/padrino-admin/bootstrap-less/grid.less +0 -100
  76. data/lib/padrino-admin/bootstrap-less/input-groups.less +0 -157
  77. data/lib/padrino-admin/bootstrap-less/jumbotron.less +0 -44
  78. data/lib/padrino-admin/bootstrap-less/labels.less +0 -64
  79. data/lib/padrino-admin/bootstrap-less/list-group.less +0 -110
  80. data/lib/padrino-admin/bootstrap-less/media.less +0 -56
  81. data/lib/padrino-admin/bootstrap-less/mixins.less +0 -926
  82. data/lib/padrino-admin/bootstrap-less/modals.less +0 -138
  83. data/lib/padrino-admin/bootstrap-less/navbar.less +0 -616
  84. data/lib/padrino-admin/bootstrap-less/navs.less +0 -242
  85. data/lib/padrino-admin/bootstrap-less/normalize.less +0 -423
  86. data/lib/padrino-admin/bootstrap-less/padrino-admin.less +0 -18
  87. data/lib/padrino-admin/bootstrap-less/pager.less +0 -55
  88. data/lib/padrino-admin/bootstrap-less/pagination.less +0 -88
  89. data/lib/padrino-admin/bootstrap-less/panels.less +0 -230
  90. data/lib/padrino-admin/bootstrap-less/popovers.less +0 -133
  91. data/lib/padrino-admin/bootstrap-less/print.less +0 -101
  92. data/lib/padrino-admin/bootstrap-less/progress-bars.less +0 -80
  93. data/lib/padrino-admin/bootstrap-less/responsive-utilities.less +0 -93
  94. data/lib/padrino-admin/bootstrap-less/scaffolding.less +0 -134
  95. data/lib/padrino-admin/bootstrap-less/tables.less +0 -233
  96. data/lib/padrino-admin/bootstrap-less/theme.less +0 -247
  97. data/lib/padrino-admin/bootstrap-less/thumbnails.less +0 -36
  98. data/lib/padrino-admin/bootstrap-less/tooltip.less +0 -95
  99. data/lib/padrino-admin/bootstrap-less/type.less +0 -296
  100. data/lib/padrino-admin/bootstrap-less/utilities.less +0 -56
  101. data/lib/padrino-admin/bootstrap-less/variables.less +0 -827
  102. data/lib/padrino-admin/bootstrap-less/wells.less +0 -29
  103. data/lib/padrino-admin/generators/templates/account/couchrest.rb.tt +0 -67
  104. data/lib/padrino-admin/generators/templates/account/datamapper.rb.tt +0 -61
  105. data/lib/padrino-admin/generators/templates/account/dynamoid.rb.tt +0 -55
  106. data/lib/padrino-admin/generators/templates/account/minirecord.rb.tt +0 -44
  107. data/lib/padrino-admin/generators/templates/account/mongomapper.rb.tt +0 -47
  108. data/lib/padrino-admin/generators/templates/account/ohm.rb.tt +0 -70
  109. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-1.11.0.min.js +0 -4
  110. data/lib/padrino-admin/locale/orm/cs.yml +0 -12
  111. data/lib/padrino-admin/locale/orm/da.yml +0 -12
  112. data/lib/padrino-admin/locale/orm/de.yml +0 -12
  113. data/lib/padrino-admin/locale/orm/en.yml +0 -12
  114. data/lib/padrino-admin/locale/orm/es.yml +0 -12
  115. data/lib/padrino-admin/locale/orm/fr.yml +0 -12
  116. data/lib/padrino-admin/locale/orm/hu.yml +0 -12
  117. data/lib/padrino-admin/locale/orm/it.yml +0 -12
  118. data/lib/padrino-admin/locale/orm/ja.yml +0 -12
  119. data/lib/padrino-admin/locale/orm/lv.yml +0 -12
  120. data/lib/padrino-admin/locale/orm/nl.yml +0 -12
  121. data/lib/padrino-admin/locale/orm/no.yml +0 -12
  122. data/lib/padrino-admin/locale/orm/pt_br.yml +0 -12
  123. data/lib/padrino-admin/locale/orm/ro.yml +0 -12
  124. data/lib/padrino-admin/locale/orm/ru.yml +0 -12
  125. data/lib/padrino-admin/locale/orm/sv.yml +0 -12
  126. data/lib/padrino-admin/locale/orm/tr.yml +0 -12
  127. data/lib/padrino-admin/locale/orm/uk.yml +0 -12
  128. data/lib/padrino-admin/locale/orm/zh_cn.yml +0 -12
  129. data/lib/padrino-admin/locale/orm/zh_tw.yml +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b9ada1e1445197975d4474ddd29280709f9587fa4733daa4f539c9ca735b5dc
4
- data.tar.gz: c59747b4b8ea966625bc9b41cac768b40e9afcefe4381943eca24f9fc94fa0f0
3
+ metadata.gz: c033740234fd9e4af9f6c01e8af2090eed03e89faaaf5ba0a1dd9f49d2b53701
4
+ data.tar.gz: b94058c0063610c456ca57fbc873074c21ebf5e968a644a63cf8ef2ea1decfd8
5
5
  SHA512:
6
- metadata.gz: 7aff29502db814e8c6ea8eb29c5c4604d9b7c8699740dabdd69dbe5a74a0a2c66fc73b8a5de11ab30ba2c6f0a917da932eedca7133b14396e1fda832a0edb1db
7
- data.tar.gz: b2caaa43c99d3684fd0c6698fb1eca36b98b2198bb54a0785af0080c711f1a1a7efe94b60891a007a02fecf4258e61ad7d676637b5b7befefa1a44108c896f1e
6
+ metadata.gz: 1fb1835134b7dea8f57e7d575f0e46d9512ebaf167e8305c16143c2c0c072af41c63f61fc154ab359ee3ad32f71860da728e766a6c79cd413027e035a457531b
7
+ data.tar.gz: 2076c252e57b13dcdfb6a66ee01219114aa1ab6fbf758036fea60b6b3c9e20582072b9c2fe5f298c6acbc6ee979983f4fe1a26d0a37a28a05bd3243f0a9a6f85
data/README.rdoc CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Padrino has a beautiful Admin management dashboard with these features:
6
6
 
7
- Orm Agnostic:: Data Adapters for Datamapper, Activerecord, Sequel, Mongomapper, Mongoid, Couchrest, Dynamoid
7
+ Orm Agnostic:: Data Adapters for Activerecord, Sequel, Mongoid
8
8
  Template Agnostic:: Erb, Erubis and Haml Renderer
9
9
  Authentication:: Support for Account authentication, Account Permission management
10
10
  Scaffold:: You can simply create a new "admin interface" by providing a Model
@@ -27,7 +27,7 @@ Create the admin subapplication:
27
27
  Next, follow the admin setup steps:
28
28
 
29
29
  * configure your <tt>config/database.rb</tt> to connect to the correct data.
30
- * run <tt>padrino rake dm:migrate</tt> # or ar:migrate if you use activerecord
30
+ * run <tt>padrino rake ar:migrate</tt> # if you use activerecord
31
31
  * run <tt>padrino rake seed</tt>
32
32
 
33
33
  Your admin panel now is ready and you can start your server with <tt>padrino start</tt> and point your browser to <tt>/admin</tt>!
@@ -35,7 +35,7 @@ Your admin panel now is ready and you can start your server with <tt>padrino sta
35
35
  To create a new "scaffold" you need to provide only a Model name to the command:
36
36
 
37
37
  demo$ padrino-gen model post --skip-migration # edit your post.rb model and add some fields
38
- demo$ padrino-gen rake dm:auto:migrate
38
+ demo$ padrino-gen rake ar:migrate
39
39
  demo$ padrino-gen admin_page post
40
40
  demo$ padrino start # and go to http://localhost:3000/admin
41
41
 
data/Rakefile CHANGED
@@ -1,22 +1 @@
1
1
  require_relative '../gem_rake_helper'
2
-
3
- namespace :bootstrap do
4
- desc 'Compile bootstrap assets'
5
- task :compile do
6
- puts 'Implement'
7
- end
8
-
9
- desc 'Compile css'
10
- task :css do
11
- require 'less'
12
- src = File.join(__dir__, 'lib', 'padrino-admin', 'bootstrap-less', 'padrino-admin.less')
13
- out = File.join(__dir__, 'lib', 'padrino-admin', 'generators', 'templates', 'assets', 'stylesheets', 'bootstrap.css')
14
- css = Less::Parser.new(paths: [File.dirname(src)]).parse(File.read(src)).to_css(compress: true)
15
- File.open(out, 'w') { |file| file.write css }
16
- puts "Generated #{out}"
17
- end
18
-
19
- desc 'Compile js'
20
- task :js do
21
- end
22
- end
@@ -44,7 +44,7 @@ module Padrino
44
44
  # We map project modules for a given role or roles.
45
45
  #
46
46
  def roles_for(*roles, &block)
47
- raise Padrino::Admin::AccessControlError, "Role #{role} must be present and must be a symbol!" if roles.any? { |r| !r.is_a?(Symbol) } || roles.empty?
47
+ raise Padrino::Admin::AccessControlError, "Role #{roles.inspect} must be present and must be a symbol!" if roles.any? { |r| !r.is_a?(Symbol) } || roles.empty?
48
48
  raise Padrino::Admin::AccessControlError, "You can't merge :any with other roles" if roles.size > 1 && roles.any? { |r| r == :any }
49
49
 
50
50
  @roles += roles
@@ -104,6 +104,8 @@ module Padrino
104
104
  #
105
105
  def allowed?(account = nil, path = nil)
106
106
  path = '/' if path.nil? || path.empty?
107
+ # Compare the decoded path; scrub invalid UTF-8 ('/%ff') so Regexp rules can match it.
108
+ path = Rack::Utils.unescape_path(path.b).force_encoding(Encoding::UTF_8).scrub
107
109
  role = account.role.to_sym rescue nil
108
110
  authorizations = @authorizations.find_all { |auth| auth.roles.include?(:any) }
109
111
  allowed_paths = authorizations.map(&:allowed).flatten.uniq
@@ -118,10 +120,21 @@ module Padrino
118
120
  denied_paths += authorizations.map(&:allowed).flatten.uniq # remove paths explicitly allowed for other roles
119
121
  denied_paths += authorizations.map(&:denied).flatten.uniq # remove paths explicitly denied to other roles
120
122
  end
121
- return true if allowed_paths.any? { |p| path =~ /^#{p}/ }
122
- return false if denied_paths.any? { |p| path =~ /^#{p}/ }
123
+ return true if allowed_paths.any? { |p| path_within?(path, p) }
124
+ return false if denied_paths.any? { |p| path.start_with?(p) } # loose on purpose: fails closed
123
125
  true
124
126
  end
127
+
128
+ private
129
+
130
+ # An allowed path covers itself, its sub-paths and its formats ('/posts' allows '/posts',
131
+ # '/posts/1' and '/posts.json'), but not unrelated paths that merely start with it ('/posts_admin').
132
+ # A Regexp matches at the start of the path, as on the deny side.
133
+ def path_within?(path, prefix)
134
+ return path.start_with?(prefix) if prefix.is_a?(Regexp) || prefix.end_with?('/', '.')
135
+
136
+ path == prefix || path.start_with?("#{prefix}/", "#{prefix}.")
137
+ end
125
138
  end
126
139
 
127
140
  ###
@@ -175,7 +188,7 @@ module Padrino
175
188
  # Returns the name of the project module humanize them for you.
176
189
  #
177
190
  def human_name
178
- @name.to_s.humanize
191
+ Padrino::Inflections.humanize(@name)
179
192
  end
180
193
 
181
194
  ##
@@ -27,7 +27,7 @@ module Padrino
27
27
  # Tell us for now which orm we support
28
28
  #
29
29
  def supported_orm
30
- %i[minirecord datamapper activerecord mongomapper mongoid couchrest sequel ohm dynamoid]
30
+ %i[activerecord mongoid sequel]
31
31
  end
32
32
 
33
33
  ##
@@ -34,7 +34,6 @@ module Padrino
34
34
  class_option :renderer, type: :string, aliases: '-e', desc: 'Rendering engine (erb, haml, slim)'
35
35
  class_option :admin_model, type: :string, aliases: '-m', default: 'Account', desc: 'The name of model for access controlling'
36
36
  class_option :admin_name, type: :string, aliases: '-a', default: 'admin', desc: 'The admin application name and path'
37
- # class_option :models_path, desc: 'The models destination path', default: '.', type: :string
38
37
 
39
38
  # Copies over the Padrino base admin application.
40
39
  def create_admin
@@ -42,13 +41,13 @@ module Padrino
42
41
  if in_app_root?
43
42
  unless supported_orm.include?(orm)
44
43
  say "<= At the moment, Padrino only supports #{supported_orm.join(' or ')}. Sorry!", :yellow
45
- raise SystemExit
44
+ exit 1
46
45
  end
47
46
 
48
47
  tmp_ext = options[:renderer] || fetch_component_choice(:renderer)
49
48
  unless supported_ext.include?(tmp_ext.to_sym)
50
49
  say "<= You are using '#{tmp_ext}' and for admin we only support '#{supported_ext.join(', ')}'. Please use #{supported_ext.map { |ext| "-e #{ext}" }.join(' or ')}", :yellow
51
- raise SystemExit
50
+ exit 1
52
51
  end
53
52
 
54
53
  # Get the app's namespace.
@@ -77,19 +76,19 @@ module Padrino
77
76
  RUBY
78
77
 
79
78
  unless options[:destroy]
80
- insert_middleware 'ConnectionPoolManagement', @admin_path if %i[minirecord activerecord].include?(orm)
81
- insert_middleware 'IdentityMap', @admin_path if orm == :datamapper
79
+ insert_middleware 'ConnectionPoolManagement', @admin_path if orm == :activerecord
82
80
  end
83
81
 
84
82
  params = [
85
83
  @model_singular, 'name:string', 'surname:string', 'email:string', 'crypted_password:string', 'role:string',
86
- "-a=#{options[:models_path]}",
87
84
  "-r=#{options[:root]}"
88
85
  ]
89
86
  params << '-s' if options[:skip_migration]
90
87
  params << '-d' if options[:destroy]
91
88
 
92
- Padrino::Generators::Model.start(params)
89
+ # An existing model is reused: the model generator would refuse it and exit 1.
90
+ model_exists = already_exists?(@model_name, fetch_component_choice(:namespace))
91
+ Padrino::Generators::Model.start(params) if options[:destroy] || !model_exists
93
92
  column = Struct.new(:name, :type)
94
93
  columns = %i[id name surname email].map { |col| column.new(col) }
95
94
  column_fields = [
@@ -102,7 +101,7 @@ module Padrino
102
101
  ]
103
102
 
104
103
  unless options[:destroy]
105
- admin_app = Padrino::Generators::AdminPage.new([@model_singular], root: options[:root], destroy: options[:destroy], admin_model: @model_singular, admin_name: @admin_name)
104
+ admin_app = Padrino::Generators::AdminPage.new([@model_singular], root: options[:root], destroy: options[:destroy], admin_model: @model_singular, admin_name: options[:admin_name])
106
105
  admin_app.default_orm = Padrino::Admin::Generators::Orm.new(@model_singular, orm, columns, column_fields)
107
106
  admin_app.invoke_all
108
107
  end
@@ -147,11 +146,10 @@ module Padrino
147
146
 
148
147
  instructions = []
149
148
  instructions << "Run 'bundle'"
150
- if %i[activerecord datamapper sequel].include?(orm)
149
+ if %i[activerecord sequel].include?(orm)
151
150
  instructions << "Run 'bundle exec rake db:create' if you have not created a database yet"
152
151
  instructions << "Run 'bundle exec rake db:migrate'"
153
152
  end
154
- instructions << "Now repeat after me... 'ohm mani padme hum', 'ohm mani padme hum'... :)" if orm == :ohm
155
153
  instructions << "Run 'bundle exec rake db:seed'"
156
154
  instructions << "Visit the admin panel in the browser at '/#{@admin_path}'"
157
155
  instructions.map! { |i| " #{instructions.index(i) + 1}) #{i}" }
@@ -164,7 +162,7 @@ module Padrino
164
162
  say '=' * 65, :green
165
163
  say
166
164
  else
167
- say 'You are not at the root of a Padrino application! (config/boot.rb not found)'
165
+ exit_outside_app_root!
168
166
  end
169
167
  end
170
168
  end
@@ -41,13 +41,23 @@ module Padrino
41
41
  def create_controller
42
42
  self.destination_root = options[:root]
43
43
  if in_app_root?
44
+ unless supported_orm.include?(orm)
45
+ say "<= At the moment, Padrino only supports #{supported_orm.join(' or ')}. Sorry!", :yellow
46
+ exit 1
47
+ end
48
+
44
49
  @app_name = fetch_app_name
45
50
  @admin_name = options[:admin_name].classify
46
51
  @admin_path = options[:admin_name].underscore
52
+ check_app_existence(@admin_path)
47
53
  @admin_model = options[:admin_model]
48
54
  models.each do |model|
49
55
  @orm = default_orm || Padrino::Admin::Generators::Orm.new(model, adapter)
50
- self.behavior = :revoke if options[:destroy]
56
+ if options[:destroy]
57
+ self.behavior = :revoke
58
+ else
59
+ @orm.check_model_orm
60
+ end
51
61
  empty_directory destination_root("#{@admin_path}/views/#{@orm.name_plural}")
52
62
 
53
63
  template 'templates/page/controller.rb.tt', destination_root("#{@admin_path}/controllers/#{@orm.name_plural}.rb")
@@ -59,7 +69,7 @@ module Padrino
59
69
  options[:destroy] ? remove_project_module(@orm.name_plural) : add_project_module(@orm.name_plural)
60
70
  end
61
71
  else
62
- say 'You are not at the root of a Padrino application! (config/boot.rb not found)'
72
+ exit_outside_app_root!
63
73
  end
64
74
  end
65
75
  end
@@ -4,12 +4,19 @@ module Padrino
4
4
  # Contains all admin related generator functionality.
5
5
  #
6
6
  module Generators
7
- # Defines a generic exception for the admin ORM handler.
8
- class OrmError < StandardError; end
7
+ # Defines a generic exception for the admin ORM handler. A Thor::Error, so the
8
+ # generators print its message without a backtrace and exit with status 1.
9
+ class OrmError < Thor::Error; end
9
10
 
10
11
  ##
11
12
  # Defines the generic ORM management functions used to manipulate data for admin.
12
13
  class Orm
14
+ MODEL_BASES = {
15
+ activerecord: 'ActiveRecord::Base',
16
+ mongoid: 'Mongoid::Document',
17
+ sequel: 'Sequel::Model'
18
+ }.freeze
19
+
13
20
  attr_reader :klass_name, :klass, :name_plural, :name_singular, :orm, :name_param
14
21
 
15
22
  def initialize(name, orm, columns = nil, column_fields = nil)
@@ -22,18 +29,26 @@ module Padrino
22
29
  @orm = orm.to_sym
23
30
  @columns = columns
24
31
  @column_fields = column_fields
25
- raise OrmError, "Model '#{klass_name}' could not be found!\nPerhaps you would like to run 'bundle exec padrino g model #{klass_name}' to create it first?" if @columns.nil? && @klass.nil?
32
+ end
33
+
34
+ # Raises OrmError when the model is missing or does not belong to the project's orm. Only
35
+ # needed when reading the model's columns to generate a page, so destroying a page skips it.
36
+ def check_model_orm
37
+ return unless @columns.nil?
38
+ raise OrmError, "Model '#{klass_name}' could not be found!\nPerhaps you would like to run 'bundle exec padrino g model #{klass_name}' to create it first?" if @klass.nil?
39
+
40
+ base = MODEL_BASES[orm]
41
+ return if base.nil? || @klass.ancestors.any? { |mod| mod.name == base }
42
+
43
+ raise OrmError, "Model '#{klass_name}' is not based on #{base} but the project orm is #{orm}!\nCheck the orm in your .components file."
26
44
  end
27
45
 
28
46
  def activerecord?
29
- case orm
30
- when :activerecord, :minirecord then true
31
- else false
32
- end
47
+ orm == :activerecord
33
48
  end
34
49
 
35
50
  def field_type(type)
36
- type = :string if type.nil? # couchrest-Hack to avoid the next line to fail
51
+ type = :string if type.nil?
37
52
  type = type.to_s.demodulize.downcase.to_sym unless type.is_a?(Symbol)
38
53
 
39
54
  case type
@@ -51,37 +66,12 @@ module Padrino
51
66
  @columns ||=
52
67
  case orm
53
68
  when :activerecord then @klass.columns
54
- when :minirecord then @klass.columns
55
- when :datamapper then @klass.properties.map { |p| dm_column(p) }
56
- when :couchrest then @klass.properties
57
69
  when :mongoid then @klass.fields.values.reject { |col| %w[_id _type].include?(col.name) }
58
- when :mongomapper then @klass.keys.values.reject { |key| key.name == '_id' } # On MongoMapper keys are an hash
59
70
  when :sequel then @klass.db_schema.map { |k, v| v[:type] = :text if v[:db_type] =~ /^text/i; Column.new(k, v[:type]) }
60
- when :ohm then @klass.attributes.map { |a| Column.new(a.to_s, :string) } # ohm has strings
61
- when :dynamoid then @klass.attributes.map { |k, v| Column.new(k.to_s, v[:type]) }
62
71
  else raise OrmError, "Adapter #{orm} is not yet supported!"
63
72
  end
64
73
  end
65
74
 
66
- def dm_column(property)
67
- case property
68
- when DataMapper::Property::Text
69
- Column.new(property.name, :text)
70
- when DataMapper::Property::Boolean
71
- Column.new(property.name, :boolean)
72
- when DataMapper::Property::Integer
73
- Column.new(property.name, :integer)
74
- when DataMapper::Property::Decimal
75
- Column.new(property.name, :decimal)
76
- when DataMapper::Property::Float
77
- Column.new(property.name, :float)
78
- when DataMapper::Property::String
79
- Column.new(property.name, :string)
80
- else # if all fails, lets assume its string-ish
81
- Column.new(property.name, :string)
82
- end
83
- end
84
-
85
75
  def column_fields
86
76
  excluded_columns = %w[created_at updated_at] << (orm == :mongoid ? '_id' : 'id')
87
77
  column_fields = columns.dup
@@ -97,9 +87,8 @@ module Padrino
97
87
 
98
88
  def find(params = nil)
99
89
  case orm
100
- when :activerecord, :minirecord, :mongomapper, :mongoid, :dynamoid then "#{klass_name}.find(#{params})"
101
- when :datamapper, :couchrest then "#{klass_name}.get(#{params})"
102
- when :sequel, :ohm then "#{klass_name}[#{params}]"
90
+ when :activerecord, :mongoid then "#{klass_name}.find(#{params})"
91
+ when :sequel then "#{klass_name}[#{params}]"
103
92
  else raise OrmError, "Adapter #{orm} is not yet supported!"
104
93
  end
105
94
  end
@@ -118,42 +107,36 @@ module Padrino
118
107
 
119
108
  def update_attributes(params = nil)
120
109
  case orm
121
- when :mongomapper, :mongoid, :couchrest, :dynamoid then "@#{name_singular}.update_attributes(#{params})"
122
- when :activerecord, :minirecord, :datamapper, :ohm then "@#{name_singular}.update(#{params})"
110
+ when :mongoid then "@#{name_singular}.update_attributes(#{params})"
111
+ when :activerecord then "@#{name_singular}.update(#{params})"
123
112
  when :sequel then "@#{name_singular}.modified! && @#{name_singular}.update(#{params})"
124
113
  else raise OrmError, "Adapter #{orm} is not yet supported!"
125
114
  end
126
115
  end
127
116
 
128
117
  def destroy
129
- orm == :ohm ? "#{name_singular}.delete" : "#{name_singular}.destroy"
118
+ "#{name_singular}.destroy"
130
119
  end
131
120
 
132
121
  def find_by_ids(params = nil)
133
122
  case orm
134
- when :ohm then "#{klass_name}.fetch(#{params})"
135
- when :datamapper then "#{klass_name}.all(id: #{params})"
136
123
  when :sequel then "#{klass_name}.where(id: #{params})"
137
124
  when :mongoid then "#{klass_name}.find(#{params})"
138
- when :couchrest then "#{klass_name}.all(keys: #{params})"
139
- when :dynamoid then "#{klass_name}.find(#{params})"
140
125
  else find(params)
141
126
  end
142
127
  end
143
128
 
144
129
  def multiple_destroy(params = nil)
145
130
  case orm
146
- when :ohm then "#{params}.each(&:delete)"
147
131
  when :sequel then "#{params}.destroy"
148
- when :datamapper then "#{params}.destroy"
149
- when :couchrest, :mongoid, :mongomapper, :dynamoid then "#{params}.each(&:destroy)"
132
+ when :mongoid then "#{params}.each(&:destroy)"
150
133
  else "#{klass_name}.destroy #{params}"
151
134
  end
152
135
  end
153
136
 
154
137
  def has_error(field)
155
138
  case orm
156
- when :datamapper, :ohm, :sequel then "@#{name_singular}.errors.key?(:#{field}) && @#{name_singular}.errors[:#{field}].count > 0"
139
+ when :sequel then "@#{name_singular}.errors.key?(:#{field}) && @#{name_singular}.errors[:#{field}].count > 0"
157
140
  else "@#{name_singular}.errors.include?(:#{field})"
158
141
  end
159
142
  end
@@ -5,8 +5,13 @@
5
5
  # name = shell.ask("What's your name?")
6
6
  # shell.say name
7
7
  #
8
- email = shell.ask "Which email do you want use for logging into admin?"
9
- password = shell.ask "Tell me the password to use:", echo: false
8
+ if $stdin.tty?
9
+ email = shell.ask "Which email do you want use for logging into admin?"
10
+ password = shell.ask "Tell me the password to use:", echo: false
11
+ else
12
+ # Piped input (CI, Docker): the email and the password on two lines, not echoed.
13
+ email, password = 2.times.map { $stdin.gets.to_s.strip }
14
+ end
10
15
 
11
16
  shell.say ""
12
17
 
@@ -8,11 +8,7 @@
8
8
  set_current_account(account)
9
9
  redirect url(:base, :index)
10
10
  elsif Padrino.env == :development && params[:bypass]
11
- <%- if @orm == :ohm -%>
12
- account = <%= @model_name %>.all.first
13
- <%- else -%>
14
11
  account = <%= @model_name %>.first
15
- <%- end -%>
16
12
  set_current_account(account)
17
13
  redirect url(:base, :index)
18
14
  else
@@ -26,7 +26,10 @@ module <%= @app_name %>
26
26
  set :admin_model, '<%= @model_name %>'
27
27
  set :login_page, '/sessions/new'
28
28
 
29
- enable :sessions
29
+ # The session cookie is Secure (sent over HTTPS only) in production. Over plain HTTP
30
+ # the app then sends no session cookie, so logins and CSRF-protected forms fail. Behind
31
+ # a proxy that terminates TLS, have it send X-Forwarded-Proto: https, or set secure: false.
32
+ set :sessions, same_site: :lax, secure: Padrino.env == :production
30
33
  disable :store_location
31
34
 
32
35
  access_control.roles_for :any do |role|
@@ -1,8 +1,8 @@
1
1
  /* ========================================================================
2
- * Bootstrap: affix.js v3.1.0
3
- * http://getbootstrap.com/javascript/#affix
2
+ * Bootstrap: affix.js v3.4.1
3
+ * https://getbootstrap.com/docs/3.4/javascript/#affix
4
4
  * ========================================================================
5
- * Copyright 2011-2014 Twitter, Inc.
5
+ * Copyright 2011-2019 Twitter, Inc.
6
6
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
7
7
  * ======================================================================== */
8
8
 
@@ -15,28 +15,56 @@
15
15
 
16
16
  var Affix = function (element, options) {
17
17
  this.options = $.extend({}, Affix.DEFAULTS, options)
18
- this.$window = $(window)
18
+
19
+ var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)
20
+
21
+ this.$target = target
19
22
  .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
20
23
  .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
21
24
 
22
25
  this.$element = $(element)
23
- this.affixed =
24
- this.unpin =
26
+ this.affixed = null
27
+ this.unpin = null
25
28
  this.pinnedOffset = null
26
29
 
27
30
  this.checkPosition()
28
31
  }
29
32
 
30
- Affix.RESET = 'affix affix-top affix-bottom'
33
+ Affix.VERSION = '3.4.1'
34
+
35
+ Affix.RESET = 'affix affix-top affix-bottom'
31
36
 
32
37
  Affix.DEFAULTS = {
33
- offset: 0
38
+ offset: 0,
39
+ target: window
40
+ }
41
+
42
+ Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
43
+ var scrollTop = this.$target.scrollTop()
44
+ var position = this.$element.offset()
45
+ var targetHeight = this.$target.height()
46
+
47
+ if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
48
+
49
+ if (this.affixed == 'bottom') {
50
+ if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
51
+ return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
52
+ }
53
+
54
+ var initializing = this.affixed == null
55
+ var colliderTop = initializing ? scrollTop : position.top
56
+ var colliderHeight = initializing ? targetHeight : height
57
+
58
+ if (offsetTop != null && scrollTop <= offsetTop) return 'top'
59
+ if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
60
+
61
+ return false
34
62
  }
35
63
 
36
64
  Affix.prototype.getPinnedOffset = function () {
37
65
  if (this.pinnedOffset) return this.pinnedOffset
38
66
  this.$element.removeClass(Affix.RESET).addClass('affix')
39
- var scrollTop = this.$window.scrollTop()
67
+ var scrollTop = this.$target.scrollTop()
40
68
  var position = this.$element.offset()
41
69
  return (this.pinnedOffset = position.top - scrollTop)
42
70
  }
@@ -48,43 +76,41 @@
48
76
  Affix.prototype.checkPosition = function () {
49
77
  if (!this.$element.is(':visible')) return
50
78
 
51
- var scrollHeight = $(document).height()
52
- var scrollTop = this.$window.scrollTop()
53
- var position = this.$element.offset()
79
+ var height = this.$element.height()
54
80
  var offset = this.options.offset
55
81
  var offsetTop = offset.top
56
82
  var offsetBottom = offset.bottom
57
-
58
- if (this.affixed == 'top') position.top += scrollTop
83
+ var scrollHeight = Math.max($(document).height(), $(document.body).height())
59
84
 
60
85
  if (typeof offset != 'object') offsetBottom = offsetTop = offset
61
86
  if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
62
87
  if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
63
88
 
64
- var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false :
65
- offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
66
- offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false
89
+ var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
67
90
 
68
- if (this.affixed === affix) return
69
- if (this.unpin) this.$element.css('top', '')
91
+ if (this.affixed != affix) {
92
+ if (this.unpin != null) this.$element.css('top', '')
70
93
 
71
- var affixType = 'affix' + (affix ? '-' + affix : '')
72
- var e = $.Event(affixType + '.bs.affix')
94
+ var affixType = 'affix' + (affix ? '-' + affix : '')
95
+ var e = $.Event(affixType + '.bs.affix')
73
96
 
74
- this.$element.trigger(e)
97
+ this.$element.trigger(e)
75
98
 
76
- if (e.isDefaultPrevented()) return
99
+ if (e.isDefaultPrevented()) return
77
100
 
78
- this.affixed = affix
79
- this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
101
+ this.affixed = affix
102
+ this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
80
103
 
81
- this.$element
82
- .removeClass(Affix.RESET)
83
- .addClass(affixType)
84
- .trigger($.Event(affixType.replace('affix', 'affixed')))
104
+ this.$element
105
+ .removeClass(Affix.RESET)
106
+ .addClass(affixType)
107
+ .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
108
+ }
85
109
 
86
110
  if (affix == 'bottom') {
87
- this.$element.offset({ top: scrollHeight - offsetBottom - this.$element.height() })
111
+ this.$element.offset({
112
+ top: scrollHeight - height - offsetBottom
113
+ })
88
114
  }
89
115
  }
90
116
 
@@ -92,9 +118,7 @@
92
118
  // AFFIX PLUGIN DEFINITION
93
119
  // =======================
94
120
 
95
- var old = $.fn.affix
96
-
97
- $.fn.affix = function (option) {
121
+ function Plugin(option) {
98
122
  return this.each(function () {
99
123
  var $this = $(this)
100
124
  var data = $this.data('bs.affix')
@@ -105,6 +129,9 @@
105
129
  })
106
130
  }
107
131
 
132
+ var old = $.fn.affix
133
+
134
+ $.fn.affix = Plugin
108
135
  $.fn.affix.Constructor = Affix
109
136
 
110
137
 
@@ -127,10 +154,10 @@
127
154
 
128
155
  data.offset = data.offset || {}
129
156
 
130
- if (data.offsetBottom) data.offset.bottom = data.offsetBottom
131
- if (data.offsetTop) data.offset.top = data.offsetTop
157
+ if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
158
+ if (data.offsetTop != null) data.offset.top = data.offsetTop
132
159
 
133
- $spy.affix(data)
160
+ Plugin.call($spy, data)
134
161
  })
135
162
  })
136
163