ditty 0.9.1 → 0.10.1

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/.env.test +2 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +23 -4
  5. data/.travis.yml +2 -7
  6. data/Gemfile.ci +0 -15
  7. data/ditty.gemspec +19 -15
  8. data/lib/ditty.rb +2 -2
  9. data/lib/ditty/cli.rb +5 -0
  10. data/lib/ditty/components/ditty.rb +4 -7
  11. data/lib/ditty/controllers/application_controller.rb +6 -5
  12. data/lib/ditty/controllers/audit_logs_controller.rb +2 -0
  13. data/lib/ditty/controllers/auth_controller.rb +5 -2
  14. data/lib/ditty/controllers/component_controller.rb +3 -3
  15. data/lib/ditty/controllers/user_login_traits_controller.rb +28 -1
  16. data/lib/ditty/controllers/users_controller.rb +3 -2
  17. data/lib/ditty/db.rb +4 -3
  18. data/lib/ditty/emails/base.rb +32 -30
  19. data/lib/ditty/generators/crud_generator.rb +51 -41
  20. data/lib/ditty/generators/project_generator.rb +1 -0
  21. data/lib/ditty/helpers/pundit.rb +4 -4
  22. data/lib/ditty/helpers/response.rb +6 -11
  23. data/lib/ditty/helpers/views.rb +21 -3
  24. data/lib/ditty/listener.rb +1 -1
  25. data/lib/ditty/models/base.rb +5 -0
  26. data/lib/ditty/models/identity.rb +7 -7
  27. data/lib/ditty/models/user.rb +9 -1
  28. data/lib/ditty/policies/user_policy.rb +1 -1
  29. data/lib/ditty/services/authentication.rb +19 -9
  30. data/lib/ditty/services/email.rb +13 -13
  31. data/lib/ditty/services/logger.rb +26 -20
  32. data/lib/ditty/services/pagination_wrapper.rb +7 -5
  33. data/lib/ditty/services/settings.rb +7 -6
  34. data/lib/ditty/tasks/ditty.rake +2 -1
  35. data/lib/ditty/templates/application.rb +1 -1
  36. data/lib/ditty/templates/config.ru +2 -2
  37. data/lib/ditty/templates/controller.rb.erb +7 -1
  38. data/{public → lib/ditty/templates/public}/browserconfig.xml +0 -0
  39. data/{public → lib/ditty/templates/public}/css/styles.css +0 -0
  40. data/lib/ditty/templates/public/favicon.ico +0 -0
  41. data/{public → lib/ditty/templates/public}/images/apple-icon.png +0 -0
  42. data/{public → lib/ditty/templates/public}/images/favicon-16x16.png +0 -0
  43. data/{public → lib/ditty/templates/public}/images/favicon-32x32.png +0 -0
  44. data/{public → lib/ditty/templates/public}/images/launcher-icon-1x.png +0 -0
  45. data/{public → lib/ditty/templates/public}/images/launcher-icon-2x.png +0 -0
  46. data/{public → lib/ditty/templates/public}/images/launcher-icon-4x.png +0 -0
  47. data/{public → lib/ditty/templates/public}/images/mstile-150x150.png +0 -0
  48. data/{public → lib/ditty/templates/public}/images/safari-pinned-tab.svg +0 -0
  49. data/{public → lib/ditty/templates/public}/js/scripts.js +0 -0
  50. data/{public/manifest.json → lib/ditty/templates/public/manifest.json.erb} +2 -2
  51. data/lib/ditty/templates/settings.yml.erb +1 -0
  52. data/lib/ditty/templates/spec_helper.rb +1 -1
  53. data/lib/ditty/templates/views/display.haml.tt +1 -1
  54. data/lib/ditty/templates/views/edit.haml.tt +1 -1
  55. data/lib/ditty/templates/views/index.haml.tt +1 -1
  56. data/lib/ditty/templates/views/new.haml.tt +1 -1
  57. data/lib/ditty/version.rb +1 -1
  58. data/spec/ditty/api_spec.rb +1 -1
  59. data/spec/ditty/emails/base_spec.rb +3 -3
  60. data/spec/ditty/emails/forgot_password_spec.rb +3 -2
  61. data/spec/ditty/models/user_spec.rb +3 -3
  62. data/spec/ditty/services/logger_spec.rb +7 -6
  63. data/spec/ditty/services/settings_spec.rb +2 -2
  64. data/spec/factories.rb +4 -4
  65. data/spec/spec_helper.rb +5 -1
  66. data/views/403.haml +1 -1
  67. data/views/500.haml +11 -0
  68. data/views/audit_logs/index.haml +12 -11
  69. data/views/auth/forgot_password.haml +29 -24
  70. data/views/auth/ldap.haml +1 -1
  71. data/views/auth/login.haml +3 -2
  72. data/views/auth/register.haml +3 -2
  73. data/views/auth/reset_password.haml +36 -19
  74. data/views/blank.haml +1 -0
  75. data/views/embedded.haml +17 -11
  76. data/views/layout.haml +16 -8
  77. data/views/partials/actions.haml +15 -14
  78. data/views/partials/filter_control.haml +1 -1
  79. data/views/partials/footer.haml +10 -2
  80. data/views/partials/form_tag.haml +1 -1
  81. data/views/partials/navitems.haml +25 -27
  82. data/views/partials/pager.haml +44 -25
  83. data/views/partials/search.haml +14 -9
  84. data/views/partials/sidebar.haml +2 -2
  85. data/views/partials/sort_ui.haml +2 -0
  86. data/views/partials/timespan_selector.haml +64 -0
  87. data/views/partials/topbar.haml +0 -15
  88. data/views/partials/user_associations.haml +32 -0
  89. data/views/quick_start.haml +23 -0
  90. data/views/roles/display.haml +3 -3
  91. data/views/roles/edit.haml +1 -1
  92. data/views/roles/index.haml +2 -2
  93. data/views/roles/new.haml +1 -1
  94. data/views/user_login_traits/display.haml +1 -1
  95. data/views/user_login_traits/edit.haml +1 -1
  96. data/views/user_login_traits/index.haml +23 -25
  97. data/views/user_login_traits/new.haml +1 -1
  98. data/views/users/display.haml +5 -5
  99. data/views/users/edit.haml +1 -1
  100. data/views/users/index.haml +5 -5
  101. data/views/users/login_traits.haml +2 -2
  102. data/views/users/new.haml +1 -1
  103. data/views/users/profile.haml +4 -4
  104. data/views/users/user.haml +1 -1
  105. metadata +116 -54
@@ -31,6 +31,8 @@ module Ditty
31
31
  def create_model
32
32
  filename = File.join("lib/#{folder}/models", "#{model_name.underscore}.rb")
33
33
  template '../templates/model.rb.erb', filename
34
+ rescue StandardError => e
35
+ puts "Could not generate model for #{model_name}: #{e.message}"
34
36
  end
35
37
 
36
38
  def create_controller
@@ -38,16 +40,22 @@ module Ditty
38
40
  template '../templates/controller.rb.erb', filename
39
41
  # TODO: Insert the route into the component file
40
42
  # insert_into_file 'config.ru', "use #{class_name}\n", after: 'run ApplicationController\n'
43
+ rescue StandardError => e
44
+ puts "Could not generate controller for #{model_name}: #{e.message}"
41
45
  end
42
46
 
43
47
  def create_policy
44
48
  filename = File.join("lib/#{folder}/policies", "#{policy_name.underscore}.rb")
45
49
  template '../templates/policy.rb.erb', filename
50
+ rescue StandardError => e
51
+ puts "Could not generate policy for #{model_name}: #{e.message}"
46
52
  end
47
53
 
48
54
  def create_type
49
55
  filename = File.join("lib/#{folder}/types", "#{model_name.underscore}_type.rb")
50
56
  template '../templates/type.rb.erb', filename
57
+ rescue StandardError => e
58
+ puts "Could not generate type for #{model_name}: #{e.message}"
51
59
  end
52
60
 
53
61
  def create_views
@@ -58,47 +66,49 @@ module Ditty
58
66
 
59
67
  private
60
68
 
61
- def meta_columns
62
- %i[id guid slug created_at updated_at]
63
- end
64
-
65
- def columns
66
- require "#{folder}/models/#{model_name.underscore}"
67
- name.constantize.columns
68
- rescue StandardError
69
- []
70
- end
71
-
72
- def schema
73
- require "#{folder}/models/#{model_name.underscore}"
74
- name.constantize.db_schema
75
- rescue StandardError
76
- []
77
- end
78
-
79
- def many_to_ones
80
- DB.foreign_key_list(model_name.underscore.pluralize)
81
- end
82
-
83
- def name_column(table)
84
- candidates = DB.schema(table.to_sym).to_h.keys - DB.foreign_key_list(table.to_sym).map { |e| e[:columns] }.flatten
85
- (candidates - meta_columns).first
86
- end
87
-
88
- def graphql_types
89
- @graphql_types ||= Hash.new('String').merge(
90
- integer: 'Integer',
91
- boolean: 'Boolean',
92
- datetime: 'GraphQL::Types::ISO8601DateTime'
93
- )
94
- end
95
-
96
- def input_types
97
- @input_types ||= Hash.new('text').merge(
98
- integer: 'number',
99
- datetime: 'date'
100
- )
101
- end
69
+ def meta_columns
70
+ %i[id guid slug created_at updated_at]
71
+ end
72
+
73
+ def columns
74
+ require "#{folder}/models/#{model_name.underscore}"
75
+ name.constantize.columns
76
+ rescue StandardError
77
+ []
78
+ end
79
+
80
+ def schema
81
+ require "#{folder}/models/#{model_name.underscore}"
82
+ name.constantize.db_schema
83
+ rescue StandardError
84
+ []
85
+ end
86
+
87
+ def many_to_ones
88
+ DB.foreign_key_list(model_name.underscore.pluralize)
89
+ end
90
+
91
+ def name_column(table)
92
+ candidates = DB.schema(table.to_sym).to_h.keys - DB.foreign_key_list(table.to_sym).map do |e|
93
+ e[:columns]
94
+ end.flatten
95
+ (candidates - meta_columns).first
96
+ end
97
+
98
+ def graphql_types
99
+ @graphql_types ||= Hash.new('String').merge(
100
+ integer: 'Integer',
101
+ boolean: 'Boolean',
102
+ datetime: 'GraphQL::Types::ISO8601DateTime'
103
+ )
104
+ end
105
+
106
+ def input_types
107
+ @input_types ||= Hash.new('text').merge(
108
+ integer: 'number',
109
+ datetime: 'date'
110
+ )
111
+ end
102
112
  end
103
113
  end
104
114
  end
@@ -27,6 +27,7 @@ module Ditty
27
27
  directory 'logs'
28
28
  directory 'pids'
29
29
  directory 'public'
30
+ directory '../../../views', 'views'
30
31
  copy_file '.gitignore', './.gitignore'
31
32
  copy_file 'env.example', './.env'
32
33
  copy_file '.rubocop.yml', './.rubocop.yml'
@@ -16,10 +16,10 @@ module Ditty
16
16
  policy = policy(record)
17
17
  action ||= record.new? ? :create : :update
18
18
  method_name = if policy.respond_to?("permitted_attributes_for_#{action}")
19
- "permitted_attributes_for_#{action}"
20
- else
21
- 'permitted_attributes'
22
- end
19
+ "permitted_attributes_for_#{action}"
20
+ else
21
+ 'permitted_attributes'
22
+ end
23
23
  policy.public_send(method_name)
24
24
  end
25
25
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'csv'
4
4
 
5
- require 'csv'
6
-
7
5
  module Ditty
8
6
  module Helpers
9
7
  module Response
@@ -27,6 +25,7 @@ module Ditty
27
25
  )
28
26
  end
29
27
  format.csv do
28
+ attachment "#{base_path}.csv"
30
29
  CSV.generate do |csv|
31
30
  csv << result.first.for_csv.keys
32
31
  result.all.each do |r|
@@ -41,7 +40,7 @@ module Ditty
41
40
  respond_to do |format|
42
41
  format.html do
43
42
  flash[:success] = "#{heading} Created"
44
- redirect with_layout(flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
43
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
45
44
  end
46
45
  format.json do
47
46
  content_type :json
@@ -62,6 +61,8 @@ module Ditty
62
61
  respond_to do |format|
63
62
  format.html do
64
63
  title = heading(:read) + (entity.respond_to?(:name) ? ": #{entity.name}" : '')
64
+ last_modified entity.updated_at if entity.respond_to?(:updated_at)
65
+ etag entity.etag if entity.respond_to?(:etag)
65
66
  haml :"#{view_location}/display",
66
67
  locals: { entity: entity, title: title, actions: actions },
67
68
  layout: layout
@@ -76,12 +77,6 @@ module Ditty
76
77
  csv << entity.for_csv.values
77
78
  end
78
79
  end
79
- format.csv do
80
- CSV.generate do |csv|
81
- csv << entity.for_csv.keys
82
- csv << entity.for_csv.values
83
- end
84
- end
85
80
  end
86
81
  end
87
82
 
@@ -90,7 +85,7 @@ module Ditty
90
85
  format.html do
91
86
  # TODO: Ability to customize the return path and message?
92
87
  flash[:success] = "#{heading} Updated"
93
- redirect with_layout(flash[:redirect_to] || back || "#{base_path}/#{entity.display_id}")
88
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
94
89
  end
95
90
  format.json do
96
91
  content_type :json
@@ -103,7 +98,7 @@ module Ditty
103
98
  respond_to do |format|
104
99
  format.html do
105
100
  flash[:success] = "#{heading} Deleted"
106
- redirect with_layout(flash[:redirect_to] || back || base_path)
101
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || back || base_path)
107
102
  end
108
103
  format.json do
109
104
  content_type :json
@@ -62,13 +62,13 @@ module Ditty
62
62
  messages = flash(key).collect do |message|
63
63
  " <div class='alert alert-#{message[0]} alert-dismissable' role='alert'>#{message[1]}</div>\n"
64
64
  end
65
- "<div id='#{id}'>\n" + messages.join + '</div>'
65
+ "<div id='#{id}'>\n#{messages.join}</div>"
66
66
  end
67
67
 
68
68
  def query_string(add = {})
69
69
  qs = params.clone.merge(add)
70
70
  qs.delete('captures')
71
- Rack::Utils.build_query qs
71
+ Rack::Utils.build_query(qs.delete_if { |_k, v| v == '' })
72
72
  end
73
73
 
74
74
  def delete_form(entity, label = 'Delete')
@@ -94,7 +94,7 @@ module Ditty
94
94
  def form_tag(url, options = {}, &block)
95
95
  options[:form_verb] ||= :post
96
96
  options[:attributes] ||= {}
97
- options[:attributes] = { 'class': 'form-horizontal' }.merge options[:attributes]
97
+ options[:attributes] = { class: 'form-horizontal' }.merge options[:attributes]
98
98
  options[:url] = options[:form_verb].to_sym == :get ? url : with_layout(url)
99
99
  haml :'partials/form_tag', locals: options.merge(block: block)
100
100
  end
@@ -138,6 +138,24 @@ module Ditty
138
138
  haml_tag :a, name, html_options
139
139
  end
140
140
  end
141
+
142
+ def sort_ui(field)
143
+ haml :'partials/sort_ui', locals: { field: field }
144
+ end
145
+
146
+ def sort_query(field)
147
+ query_string(
148
+ order: params[:sort] == field.to_s && params[:order] == 'asc' ? 'desc' : 'asc',
149
+ sort: field,
150
+ )
151
+ end
152
+
153
+ def sort_icon(field)
154
+ return 'fa-sort' unless params[:sort] == field.to_s
155
+ return 'fa-sort-up' if params[:order] == 'asc'
156
+
157
+ 'fa-sort-down'
158
+ end
141
159
  end
142
160
  end
143
161
  end
@@ -66,7 +66,7 @@ module Ditty
66
66
  def action_from(target, method)
67
67
  return method unless method.to_s.start_with? 'component_'
68
68
 
69
- target.class.to_s.demodulize.underscore + '_' + method.to_s.gsub(/^component_/, '')
69
+ "#{target.class.to_s.demodulize.underscore}_#{method.to_s.gsub(/^component_/, '')}"
70
70
  end
71
71
 
72
72
  def log_action(values)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest/sha2'
3
4
  require 'sequel'
4
5
 
5
6
  module Ditty
@@ -12,6 +13,10 @@ module Ditty
12
13
  self[:slug] || self[:guid] || self[:id]
13
14
  end
14
15
 
16
+ def etag
17
+ Digest::SHA2.hexdigest values.to_json
18
+ end
19
+
15
20
  alias for_csv for_json
16
21
  end
17
22
  end
@@ -55,7 +55,7 @@ module Ditty
55
55
  # 1 Special Character
56
56
  # 1 Number
57
57
  # At least 8 characters
58
- %r[\A(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#&$*)(}{%^=_+|\\:";'<>,.\-\/?\[\]])(?=.*[0-9]).{8,}\Z],
58
+ %r[\A(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#&$*)(}{%^=_+|\\:";'<>,.\-/?\[\]])(?=.*[0-9]).{8,}\Z],
59
59
  :password,
60
60
  message: 'is not strong enough'
61
61
  )
@@ -72,12 +72,12 @@ module Ditty
72
72
 
73
73
  private
74
74
 
75
- def encrypt_password
76
- self.crypted_password = ::BCrypt::Password.create(password)
77
- end
75
+ def encrypt_password
76
+ self.crypted_password = ::BCrypt::Password.create(password)
77
+ end
78
78
 
79
- def password_required
80
- crypted_password.blank? || !password.blank?
81
- end
79
+ def password_required
80
+ crypted_password.blank? || !password.blank?
81
+ end
82
82
  end
83
83
  end
@@ -40,7 +40,9 @@ module Ditty
40
40
  end
41
41
 
42
42
  def respond_to_missing?(name, _include_private = false)
43
- name[-1] == '?'
43
+ return true if name[-1] == '?'
44
+
45
+ super
44
46
  end
45
47
 
46
48
  def gravatar
@@ -57,6 +59,12 @@ module Ditty
57
59
  validates_format(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :email)
58
60
  end
59
61
 
62
+ def before_save
63
+ super
64
+ self.name = nil if name.blank?
65
+ self.surname = nil if surname.blank?
66
+ end
67
+
60
68
  # Add the basic roles and identity
61
69
  def after_create
62
70
  super
@@ -26,7 +26,7 @@ module Ditty
26
26
  end
27
27
 
28
28
  def delete?
29
- create?
29
+ create? && record&.super_admin? == false
30
30
  end
31
31
 
32
32
  def permitted_attributes
@@ -3,7 +3,6 @@
3
3
  require 'ditty/controllers/application_controller'
4
4
  require 'ditty/services/settings'
5
5
  require 'ditty/services/logger'
6
- require 'backports/2.4.0/hash/compact'
7
6
 
8
7
  require 'omniauth'
9
8
  OmniAuth.config.logger = ::Ditty::Services::Logger
@@ -23,26 +22,36 @@ module Ditty
23
22
  end
24
23
 
25
24
  def providers
26
- config.compact.keys
25
+ config.compact.keys.select { |e| config[e][:available] && config[e][:enabled] != false }
27
26
  end
28
27
 
29
28
  def setup
30
- providers.each do |provider|
31
- req = config.dig(provider, :require) || "omniauth/#{provider}"
32
- begin
33
- require req
29
+ config.compact.each_key do |provider|
30
+ ::Ditty::Services::Logger.debug "Loading authentication provider #{provider}"
31
+ req = if config.dig(provider, :require)
32
+ [config[provider][:require]]
33
+ else
34
+ ["omniauth/#{provider}", "omniauth-#{provider}"]
35
+ end
36
+ req.find do |e|
37
+ require e
38
+ config[provider][:available] = true
39
+ true
34
40
  rescue LoadError
35
- require "omniauth-#{provider}"
41
+ ::Ditty::Services::Logger.warn "Could not load authentication provider #{provider} using #{e}"
42
+ config[provider][:available] = false
43
+ false
36
44
  end
37
45
  end
38
46
  end
39
47
 
40
48
  def config
41
- default.merge ::Ditty::Services::Settings.values(:authentication) || {}
49
+ @config ||= default.merge(::Ditty::Services::Settings.values(:authentication) || {})
42
50
  end
43
51
 
44
52
  def provides?(provider)
45
- providers.include? provider.to_sym
53
+ provider = provider.to_sym
54
+ providers.include?(provider) && config[provider][:available] && config.dig(provider, :enabled) != false
46
55
  end
47
56
 
48
57
  def default
@@ -50,6 +59,7 @@ module Ditty
50
59
  require 'ditty/controllers/auth_controller'
51
60
  {
52
61
  identity: {
62
+ available: true,
53
63
  arguments: [
54
64
  {
55
65
  fields: [:username],
@@ -34,21 +34,21 @@ module Ditty
34
34
 
35
35
  private
36
36
 
37
- def config
38
- @config ||= default.merge ::Ditty::Services::Settings.values(:email) || {}
39
- end
37
+ def config
38
+ @config ||= default.merge ::Ditty::Services::Settings.values(:email) || {}
39
+ end
40
40
 
41
- def default
42
- {
43
- delivery_method: :logger,
44
- logger: ::Ditty::Services::Logger
45
- }
46
- end
41
+ def default
42
+ {
43
+ delivery_method: :logger,
44
+ logger: ::Ditty::Services::Logger
45
+ }
46
+ end
47
47
 
48
- def from_symbol(email, options)
49
- require "ditty/emails/#{email}"
50
- constantize("Ditty::Emails::#{classify(email)}").new(options)
51
- end
48
+ def from_symbol(email, options)
49
+ require "ditty/emails/#{email}"
50
+ constantize("Ditty::Emails::#{classify(email)}").new(options)
51
+ end
52
52
  end
53
53
  end
54
54
  end