ditty 0.8.0 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
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