ditty 0.8.0 → 0.10.2

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/.env.test +2 -0
  3. data/.gitignore +3 -0
  4. data/.pryrc +2 -0
  5. data/.rubocop.yml +23 -4
  6. data/.travis.yml +4 -8
  7. data/CNAME +1 -0
  8. data/Dockerfile +18 -0
  9. data/Gemfile.ci +0 -17
  10. data/Rakefile +2 -2
  11. data/_config.yml +1 -0
  12. data/config.ru +4 -4
  13. data/ditty.gemspec +28 -18
  14. data/docs/CNAME +1 -0
  15. data/docs/_config.yml +1 -0
  16. data/docs/index.md +34 -0
  17. data/exe/ditty +2 -0
  18. data/lib/ditty/cli.rb +41 -5
  19. data/lib/ditty/components/{app.rb → ditty.rb} +18 -16
  20. data/lib/ditty/controllers/{application.rb → application_controller.rb} +63 -34
  21. data/lib/ditty/controllers/{audit_logs.rb → audit_logs_controller.rb} +4 -2
  22. data/lib/ditty/controllers/{auth.rb → auth_controller.rb} +22 -18
  23. data/lib/ditty/controllers/{component.rb → component_controller.rb} +23 -20
  24. data/lib/ditty/controllers/{main.rb → main_controller.rb} +6 -2
  25. data/lib/ditty/controllers/roles_controller.rb +23 -0
  26. data/lib/ditty/controllers/user_login_traits_controller.rb +46 -0
  27. data/lib/ditty/controllers/{users.rb → users_controller.rb} +13 -11
  28. data/lib/ditty/db.rb +7 -5
  29. data/lib/ditty/emails/base.rb +37 -32
  30. data/lib/ditty/generators/crud_generator.rb +114 -0
  31. data/lib/ditty/generators/migration_generator.rb +26 -0
  32. data/lib/ditty/generators/project_generator.rb +52 -0
  33. data/lib/ditty/helpers/component.rb +2 -1
  34. data/lib/ditty/helpers/pundit.rb +24 -8
  35. data/lib/ditty/helpers/response.rb +15 -13
  36. data/lib/ditty/helpers/views.rb +28 -6
  37. data/lib/ditty/listener.rb +6 -4
  38. data/lib/ditty/memcached.rb +8 -0
  39. data/lib/ditty/middleware/accept_extension.rb +2 -2
  40. data/lib/ditty/middleware/error_catchall.rb +2 -2
  41. data/lib/ditty/models/base.rb +9 -0
  42. data/lib/ditty/models/identity.rb +11 -7
  43. data/lib/ditty/models/role.rb +1 -0
  44. data/lib/ditty/models/user.rb +23 -2
  45. data/lib/ditty/policies/role_policy.rb +1 -1
  46. data/lib/ditty/policies/user_login_trait_policy.rb +1 -1
  47. data/lib/ditty/policies/user_policy.rb +1 -1
  48. data/lib/ditty/services/authentication.rb +27 -16
  49. data/lib/ditty/services/email.rb +19 -15
  50. data/lib/ditty/services/logger.rb +26 -20
  51. data/lib/ditty/services/pagination_wrapper.rb +7 -5
  52. data/lib/ditty/services/settings.rb +7 -6
  53. data/lib/ditty/tasks/ditty.rake +19 -1
  54. data/lib/ditty/tasks/omniauth-ldap.rake +2 -2
  55. data/lib/ditty/templates/.gitignore +5 -0
  56. data/lib/ditty/templates/.rspec +2 -0
  57. data/lib/ditty/templates/.rubocop.yml +7 -0
  58. data/lib/ditty/templates/Rakefile +12 -0
  59. data/lib/ditty/templates/application.rb +12 -0
  60. data/lib/ditty/templates/config.ru +37 -0
  61. data/lib/ditty/templates/controller.rb.erb +64 -0
  62. data/lib/ditty/templates/env.example +4 -0
  63. data/lib/ditty/templates/lib/project.rb.erb +5 -0
  64. data/lib/ditty/templates/logs/.empty_directory +0 -0
  65. data/lib/ditty/templates/migration.rb.erb +7 -0
  66. data/lib/ditty/templates/model.rb.erb +26 -0
  67. data/lib/ditty/templates/pids/.empty_directory +0 -0
  68. data/lib/ditty/templates/policy.rb.erb +48 -0
  69. data/{public → lib/ditty/templates/public}/browserconfig.xml +0 -0
  70. data/lib/ditty/templates/public/css/sb-admin-2.min.css +10 -0
  71. data/lib/ditty/templates/public/css/styles.css +13 -0
  72. data/lib/ditty/templates/public/favicon.ico +0 -0
  73. data/{public → lib/ditty/templates/public}/images/apple-icon.png +0 -0
  74. data/{public → lib/ditty/templates/public}/images/favicon-16x16.png +0 -0
  75. data/{public → lib/ditty/templates/public}/images/favicon-32x32.png +0 -0
  76. data/{public → lib/ditty/templates/public}/images/launcher-icon-1x.png +0 -0
  77. data/{public → lib/ditty/templates/public}/images/launcher-icon-2x.png +0 -0
  78. data/{public → lib/ditty/templates/public}/images/launcher-icon-4x.png +0 -0
  79. data/{public → lib/ditty/templates/public}/images/mstile-150x150.png +0 -0
  80. data/{public → lib/ditty/templates/public}/images/safari-pinned-tab.svg +0 -0
  81. data/lib/ditty/templates/public/js/sb-admin-2.min.js +7 -0
  82. data/lib/ditty/templates/public/js/scripts.js +1 -0
  83. data/{public/manifest.json → lib/ditty/templates/public/manifest.json.erb} +2 -2
  84. data/lib/ditty/templates/settings.yml.erb +29 -0
  85. data/lib/ditty/templates/sidekiq.rb +18 -0
  86. data/lib/ditty/templates/sidekiq.yml +9 -0
  87. data/lib/ditty/templates/spec_helper.rb +43 -0
  88. data/lib/ditty/templates/type.rb.erb +21 -0
  89. data/lib/ditty/templates/views/display.haml.tt +20 -0
  90. data/lib/ditty/templates/views/edit.haml.tt +10 -0
  91. data/lib/ditty/templates/views/form.haml.tt +11 -0
  92. data/lib/ditty/templates/views/index.haml.tt +29 -0
  93. data/lib/ditty/templates/views/new.haml.tt +10 -0
  94. data/lib/ditty/version.rb +1 -1
  95. data/lib/ditty.rb +6 -4
  96. data/lib/rubocop/cop/ditty/call_services_directly.rb +2 -2
  97. data/migrate/20181209_add_user_login_traits.rb +4 -4
  98. data/migrate/20190220_add_parent_id_to_roles.rb +9 -0
  99. data/spec/ditty/api_spec.rb +51 -0
  100. data/spec/ditty/controllers/roles_spec.rb +67 -0
  101. data/spec/ditty/controllers/user_login_traits_spec.rb +72 -0
  102. data/spec/ditty/controllers/users_spec.rb +72 -0
  103. data/spec/ditty/emails/base_spec.rb +76 -0
  104. data/spec/ditty/emails/forgot_password_spec.rb +20 -0
  105. data/spec/ditty/helpers/component_spec.rb +85 -0
  106. data/spec/ditty/models/user_spec.rb +36 -0
  107. data/spec/ditty/services/email_spec.rb +36 -0
  108. data/spec/ditty/services/logger_spec.rb +68 -0
  109. data/spec/ditty/services/settings_spec.rb +63 -0
  110. data/spec/ditty_spec.rb +9 -0
  111. data/spec/factories.rb +46 -0
  112. data/spec/fixtures/logger.yml +17 -0
  113. data/spec/fixtures/section.yml +3 -0
  114. data/spec/fixtures/settings.yml +8 -0
  115. data/spec/spec_helper.rb +51 -0
  116. data/spec/support/api_shared_examples.rb +250 -0
  117. data/spec/support/crud_shared_examples.rb +145 -0
  118. data/views/403.haml +1 -1
  119. data/views/404.haml +2 -4
  120. data/views/500.haml +11 -0
  121. data/views/audit_logs/index.haml +32 -33
  122. data/views/auth/forgot_password.haml +32 -16
  123. data/views/auth/identity.haml +14 -13
  124. data/views/auth/ldap.haml +2 -2
  125. data/views/auth/login.haml +23 -17
  126. data/views/auth/register.haml +20 -18
  127. data/views/auth/register_identity.haml +27 -12
  128. data/views/auth/reset_password.haml +36 -19
  129. data/views/blank.haml +43 -0
  130. data/views/embedded.haml +17 -11
  131. data/views/index.haml +1 -1
  132. data/views/layout.haml +45 -30
  133. data/views/partials/actions.haml +15 -14
  134. data/views/partials/content_tag.haml +0 -0
  135. data/views/partials/delete_form.haml +1 -1
  136. data/views/partials/filter_control.haml +2 -2
  137. data/views/partials/footer.haml +6 -5
  138. data/views/partials/form_control.haml +19 -12
  139. data/views/partials/form_tag.haml +1 -1
  140. data/views/partials/navitems.haml +42 -0
  141. data/views/partials/notifications.haml +12 -8
  142. data/views/partials/pager.haml +44 -25
  143. data/views/partials/search.haml +15 -11
  144. data/views/partials/sidebar.haml +15 -37
  145. data/views/partials/sort_ui.haml +2 -0
  146. data/views/partials/topbar.haml +53 -0
  147. data/views/partials/user_associations.haml +32 -0
  148. data/views/quick_start.haml +23 -0
  149. data/views/roles/display.haml +27 -6
  150. data/views/roles/edit.haml +3 -3
  151. data/views/roles/form.haml +1 -0
  152. data/views/roles/index.haml +23 -16
  153. data/views/roles/new.haml +2 -2
  154. data/views/user_login_traits/display.haml +4 -4
  155. data/views/user_login_traits/edit.haml +3 -3
  156. data/views/user_login_traits/index.haml +23 -25
  157. data/views/user_login_traits/new.haml +2 -2
  158. data/views/users/display.haml +14 -15
  159. data/views/users/edit.haml +3 -3
  160. data/views/users/form.haml +0 -0
  161. data/views/users/index.haml +31 -24
  162. data/views/users/login_traits.haml +6 -8
  163. data/views/users/new.haml +2 -2
  164. data/views/users/profile.haml +15 -15
  165. data/views/users/user.haml +1 -1
  166. metadata +271 -63
  167. data/lib/ditty/controllers/roles.rb +0 -13
  168. data/lib/ditty/controllers/user_login_traits.rb +0 -18
  169. data/views/partials/navbar.haml +0 -22
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor/group'
4
+ require 'active_support/inflector'
5
+
6
+ module Ditty
7
+ module Generators
8
+ class ProjectGenerator < Thor::Group
9
+ include Thor::Actions
10
+
11
+ attr_reader :name, :namespace, :folder
12
+
13
+ desc 'Initialize a new Ditty project in the current folder'
14
+
15
+ def setup
16
+ @name = File.basename(Dir.getwd)
17
+ @folder = @name.underscore
18
+ @namespace = folder.classify
19
+ @name = @name.titleize
20
+ end
21
+
22
+ def self.source_root
23
+ File.expand_path('../templates', __dir__)
24
+ end
25
+
26
+ def create_startup_files
27
+ directory 'logs'
28
+ directory 'pids'
29
+ directory 'public'
30
+ directory '../../../views', 'views'
31
+ copy_file '.gitignore', './.gitignore'
32
+ copy_file 'env.example', './.env'
33
+ copy_file '.rubocop.yml', './.rubocop.yml'
34
+ copy_file '.rspec', './.rspec'
35
+
36
+ template 'lib/project.rb.erb', "lib/#{folder}.rb"
37
+
38
+ copy_file 'application.rb', './application.rb'
39
+ copy_file 'config.ru', './config.ru'
40
+ copy_file 'Rakefile', './Rakefile'
41
+ copy_file 'sidekiq.rb', './config/sidekiq.rb'
42
+ copy_file 'sidekiq.yml', './config/sidekiq.yml'
43
+
44
+ copy_file 'spec_helper.rb', './specs/spec_helper.rb'
45
+ copy_file '../../../spec/support/api_shared_examples.rb', './specs/support/api_shared_examples.rb'
46
+ copy_file '../../../spec/support/crud_shared_examples.rb', './specs/support/crud_shared_examples.rb'
47
+
48
+ template 'settings.yml.erb', './config/settings.yml'
49
+ end
50
+ end
51
+ end
52
+ end
@@ -33,11 +33,12 @@ module Ditty
33
33
  param :q, String
34
34
  param :page, Integer, min: 1, default: 1
35
35
  param :sort, String
36
- param :order, String, in: %w[asc desc], transform: :downcase, default: 'asc'
36
+ param :order, String, in: %w[asc desc], transform: :downcase
37
37
  # TODO: Can we dynamically validate the search / filter fields?
38
38
 
39
39
  ds = dataset
40
40
  ds = ds.dataset if ds.respond_to?(:dataset)
41
+ params[:order] ||= 'asc' if params[:sort]
41
42
  return ds if params[:count] == 'all'
42
43
 
43
44
  params[:count] = check_count
@@ -12,23 +12,39 @@ module Ditty
12
12
  super
13
13
  end
14
14
 
15
- def permitted_attributes(record, action)
16
- param_key = PolicyFinder.new(record).param_key
15
+ def permitted_attributes(record, action = nil)
17
16
  policy = policy(record)
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
+ policy.public_send(method_name)
24
+ end
23
25
 
24
- policy_fields = policy.public_send(method_name)
26
+ def permitted_parameters(record, action = nil)
27
+ param_key = PolicyFinder.new(record).param_key
28
+ policy_fields = permitted_attributes(record, action)
25
29
  request.params.fetch(param_key, {}).select do |key, _value|
26
30
  policy_fields.include? key.to_sym
27
31
  end
28
32
  end
29
33
 
34
+ def permitted_response_attributes(record, method = :values)
35
+ policy = policy(record)
36
+ response = record.send(method)
37
+
38
+ return response unless policy.respond_to? :response_attributes
39
+
40
+ policy_fields = policy.response_attributes
41
+ response.select do |key, _value|
42
+ policy_fields.include? key.to_sym
43
+ end
44
+ end
45
+
30
46
  def pundit_user
31
- current_user
47
+ current_user unless current_user&.anonymous?
32
48
  end
33
49
  end
34
50
  end
@@ -5,12 +5,12 @@ require 'csv'
5
5
  module Ditty
6
6
  module Helpers
7
7
  module Response
8
- def list_response(result)
8
+ def list_response(result, view: 'index')
9
9
  respond_to do |format|
10
10
  format.html do
11
11
  actions = {}
12
12
  actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
13
- haml :"#{view_location}/index",
13
+ haml :"#{view_location}/#{view}",
14
14
  locals: { list: result, title: heading(:list), actions: actions },
15
15
  layout: layout
16
16
  end
@@ -18,13 +18,14 @@ module Ditty
18
18
  # TODO: Add links defined by actions (New #{heading})
19
19
  total = result.respond_to?(:pagination_record_count) ? result.pagination_record_count : result.count
20
20
  json(
21
- 'items' => result.all.map(&:for_json),
21
+ 'items' => result.all.map { |e| permitted_response_attributes(e, :for_json) },
22
22
  'page' => (params['page'] || 1).to_i,
23
23
  'count' => result.count,
24
24
  'total' => total
25
25
  )
26
26
  end
27
27
  format.csv do
28
+ attachment "#{base_path}.csv"
28
29
  CSV.generate do |csv|
29
30
  csv << result.first.for_csv.keys
30
31
  result.all.each do |r|
@@ -39,18 +40,18 @@ module Ditty
39
40
  respond_to do |format|
40
41
  format.html do
41
42
  flash[:success] = "#{heading} Created"
42
- redirect with_layout("#{base_path}/#{entity.id}")
43
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
43
44
  end
44
45
  format.json do
45
46
  content_type :json
46
- redirect "#{base_path}/#{entity.id}", 201
47
+ redirect "#{base_path}/#{entity.display_id}", 201
47
48
  end
48
49
  end
49
50
  end
50
51
 
51
52
  def actions(entity = nil)
52
53
  actions = {}
53
- actions["#{base_path}/#{entity.id}/edit"] = "Edit #{heading}" if entity && policy(entity).update?
54
+ actions["#{base_path}/#{entity.display_id}/edit"] = "Edit #{heading}" if entity && policy(entity).update?
54
55
  actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
55
56
  actions
56
57
  end
@@ -60,13 +61,15 @@ module Ditty
60
61
  respond_to do |format|
61
62
  format.html do
62
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)
63
66
  haml :"#{view_location}/display",
64
67
  locals: { entity: entity, title: title, actions: actions },
65
68
  layout: layout
66
69
  end
67
70
  format.json do
68
71
  # TODO: Add links defined by actions (Edit #{heading})
69
- json entity.for_json
72
+ json permitted_response_attributes(entity, :for_json)
70
73
  end
71
74
  format.csv do
72
75
  CSV.generate do |csv|
@@ -82,11 +85,11 @@ module Ditty
82
85
  format.html do
83
86
  # TODO: Ability to customize the return path and message?
84
87
  flash[:success] = "#{heading} Updated"
85
- redirect with_layout("#{base_path}/#{entity.id}")
88
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || "#{base_path}/#{entity.display_id}")
86
89
  end
87
90
  format.json do
88
- headers 'Location' => "#{base_path}/#{entity.id}"
89
- json body entity.for_json
91
+ content_type :json
92
+ redirect "#{base_path}/#{entity.display_id}", 200, json(entity.for_json)
90
93
  end
91
94
  end
92
95
  end
@@ -95,12 +98,11 @@ module Ditty
95
98
  respond_to do |format|
96
99
  format.html do
97
100
  flash[:success] = "#{heading} Deleted"
98
- redirect with_layout(base_path.to_s)
101
+ redirect with_layout(params[:redirect_to] || flash[:redirect_to] || back || base_path)
99
102
  end
100
103
  format.json do
101
104
  content_type :json
102
- headers 'Location' => base_path.to_s
103
- status 204
105
+ redirect base_path.to_s, 204
104
106
  end
105
107
  end
106
108
  end
@@ -24,9 +24,11 @@ module Ditty
24
24
  def form_control(name, model, opts = {})
25
25
  label = opts.delete(:label) || name.to_s.titlecase
26
26
  klass = opts.delete(:class) || 'form-control' unless opts[:type] == 'file'
27
+ klass = "#{klass} is-invalid" if model.errors[name]
27
28
  group = opts.delete(:group) || model.class.to_s.demodulize.underscore
28
29
  field = opts.delete(:field) || name
29
30
  default = opts.delete(:default) || nil
31
+ help_text = opts.delete(:help_text) || nil
30
32
 
31
33
  attributes = { type: 'text', id: name, name: "#{group}[#{name}]", class: klass }.merge(opts)
32
34
  haml :'partials/form_control', locals: {
@@ -36,7 +38,8 @@ module Ditty
36
38
  name: name,
37
39
  group: group,
38
40
  field: field,
39
- default: default
41
+ default: default,
42
+ help_text: help_text
40
43
  }
41
44
  end
42
45
 
@@ -47,7 +50,8 @@ module Ditty
47
50
  haml :'partials/filter_control', locals: {
48
51
  name: filter[:name],
49
52
  label: opts[:label] || filter[:name].to_s.titlecase,
50
- options: send(meth)
53
+ options: send(meth),
54
+ total_filters: opts[:filters]
51
55
  }
52
56
  end
53
57
 
@@ -58,13 +62,13 @@ module Ditty
58
62
  messages = flash(key).collect do |message|
59
63
  " <div class='alert alert-#{message[0]} alert-dismissable' role='alert'>#{message[1]}</div>\n"
60
64
  end
61
- "<div id='#{id}'>\n" + messages.join + '</div>'
65
+ "<div id='#{id}'>\n#{messages.join}</div>"
62
66
  end
63
67
 
64
68
  def query_string(add = {})
65
69
  qs = params.clone.merge(add)
66
70
  qs.delete('captures')
67
- Rack::Utils.build_query qs
71
+ Rack::Utils.build_query(qs.delete_if { |_k, v| v == '' })
68
72
  end
69
73
 
70
74
  def delete_form(entity, label = 'Delete')
@@ -90,7 +94,7 @@ module Ditty
90
94
  def form_tag(url, options = {}, &block)
91
95
  options[:form_verb] ||= :post
92
96
  options[:attributes] ||= {}
93
- options[:attributes] = { 'class': 'form-horizontal' }.merge options[:attributes]
97
+ options[:attributes] = { class: 'form-horizontal' }.merge options[:attributes]
94
98
  options[:url] = options[:form_verb].to_sym == :get ? url : with_layout(url)
95
99
  haml :'partials/form_tag', locals: options.merge(block: block)
96
100
  end
@@ -98,7 +102,7 @@ module Ditty
98
102
  def pagination(list, base_path, qp = {})
99
103
  return unless list.respond_to?(:pagination_record_count) || list.respond_to?(:total_entries)
100
104
 
101
- list = Ditty::Services::PaginationWrapper.new(list)
105
+ list = ::Ditty::Services::PaginationWrapper.new(list)
102
106
  locals = {
103
107
  first_link: "#{base_path}?" + query_string(qp.merge(page: 1)),
104
108
  next_link: list.last_page? ? '#' : "#{base_path}?" + query_string(qp.merge(page: list.next_page)),
@@ -134,6 +138,24 @@ module Ditty
134
138
  haml_tag :a, name, html_options
135
139
  end
136
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
137
159
  end
138
160
  end
139
161
  end
@@ -17,7 +17,9 @@ module Ditty
17
17
  end
18
18
 
19
19
  def method_missing(method, *args)
20
- return unless args[0].is_a?(Hash) && args[0][:target].is_a?(Sinatra::Base) && args[0][:target].settings.track_actions
20
+ unless args[0].is_a?(Hash) && args[0][:target].is_a?(Sinatra::Base) && args[0][:target].settings.track_actions
21
+ return
22
+ end
21
23
 
22
24
  log_action(
23
25
  user_traits(args[0][:target]).merge(
@@ -64,12 +66,12 @@ module Ditty
64
66
  def action_from(target, method)
65
67
  return method unless method.to_s.start_with? 'component_'
66
68
 
67
- target.class.to_s.demodulize.underscore + '_' + method.to_s.gsub(/^component_/, '')
69
+ "#{target.class.to_s.demodulize.underscore}_#{method.to_s.gsub(/^component_/, '')}"
68
70
  end
69
71
 
70
72
  def log_action(values)
71
73
  values[:user] ||= values[:target].current_user if values[:target]
72
- @mutex.synchronize { Ditty::AuditLog.create values }
74
+ @mutex.synchronize { ::Ditty::AuditLog.create values }
73
75
  end
74
76
 
75
77
  def user_traits(target)
@@ -84,4 +86,4 @@ module Ditty
84
86
  end
85
87
  end
86
88
 
87
- Wisper.subscribe(Ditty::Listener.new) unless ENV['RACK_ENV'] == 'test'
89
+ Wisper.subscribe(::Ditty::Listener.new) unless ENV['RACK_ENV'] == 'test'
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/services/logger'
4
+
5
+ Ditty::Services::Logger.info 'Setting up caching'
6
+ require 'memcached'
7
+ CACHE = Memcached.new(ENV['MEMCACHED_URL'])
8
+ Sequel::Model.plugin :caching, CACHE, ignore_exceptions: true
@@ -8,7 +8,7 @@ module Ditty
8
8
  class AcceptExtension
9
9
  attr_reader :env, :regex, :content_type
10
10
 
11
- def initialize(app, regex = /\A(.*)\.json(\/?)\Z/, content_type = 'application/json')
11
+ def initialize(app, regex = %r{\A(.*)\.json(/?)\Z}, content_type = 'application/json')
12
12
  # @mutex = Mutex.new
13
13
  @app = app
14
14
  @regex = regex
@@ -19,7 +19,7 @@ module Ditty
19
19
  @env = env
20
20
 
21
21
  request = Rack::Request.new(env)
22
- if request.path =~ regex
22
+ if request.path&.match?(regex)
23
23
  request.path_info = request.path_info.gsub(regex, '\1\2')
24
24
  env = request.env
25
25
  env['ACCEPT'] = content_type
@@ -16,8 +16,8 @@ module Ditty
16
16
  begin
17
17
  @app.call env
18
18
  rescue StandardError => e
19
- ::Ditty::Services::Logger.instance.error "Ditty Catchall: #{e.class}"
20
- ::Ditty::Services::Logger.instance.error e
19
+ ::Ditty::Services::Logger.error "Ditty Catchall: #{e.class}"
20
+ ::Ditty::Services::Logger.error e
21
21
  [500, {}, ['Unknown Error']]
22
22
  end
23
23
  end
@@ -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
@@ -8,6 +9,14 @@ module Ditty
8
9
  values
9
10
  end
10
11
 
12
+ def display_id
13
+ self[:slug] || self[:guid] || self[:id]
14
+ end
15
+
16
+ def etag
17
+ Digest::SHA2.hexdigest values.to_json
18
+ end
19
+
11
20
  alias for_csv for_json
12
21
  end
13
22
  end
@@ -37,6 +37,10 @@ module Ditty
37
37
  }
38
38
  end
39
39
 
40
+ def uid
41
+ user&.id
42
+ end
43
+
40
44
  # Validation
41
45
  def validate
42
46
  super
@@ -55,7 +59,7 @@ module Ditty
55
59
  # 1 Special Character
56
60
  # 1 Number
57
61
  # At least 8 characters
58
- %r[\A(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#&$*)(}{%^=_+|\\:";'<>,.\-\/?\[\]])(?=.*[0-9]).{8,}\Z],
62
+ %r[\A(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#&$*)(}{%^=_+|\\:";'<>,.\-/?\[\]])(?=.*[0-9]).{8,}\Z],
59
63
  :password,
60
64
  message: 'is not strong enough'
61
65
  )
@@ -72,12 +76,12 @@ module Ditty
72
76
 
73
77
  private
74
78
 
75
- def encrypt_password
76
- self.crypted_password = ::BCrypt::Password.create(password)
77
- end
79
+ def encrypt_password
80
+ self.crypted_password = ::BCrypt::Password.create(password)
81
+ end
78
82
 
79
- def password_required
80
- crypted_password.blank? || !password.blank?
81
- end
83
+ def password_required
84
+ crypted_password.blank? || !password.blank?
85
+ end
82
86
  end
83
87
  end
@@ -5,6 +5,7 @@ require 'ditty/models/base'
5
5
  module Ditty
6
6
  class Role < ::Sequel::Model
7
7
  include ::Ditty::Base
8
+ plugin :tree
8
9
 
9
10
  many_to_many :users
10
11