ditty 0.3.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c1aeccd30ff2045fdc743913e43590e7cc5ca03
4
- data.tar.gz: 5c85c18dfd395210a4c64a11b9d801d8afaf2360
3
+ metadata.gz: 2b443ee45c38fca3c0f69379d0631b03f1f91263
4
+ data.tar.gz: 3b26f6324b6979e1e517c6338e41cb994dce80c0
5
5
  SHA512:
6
- metadata.gz: 9152764039916756b7c355de8466597f83f64921122e969f5dec13a5376c3310ab4d2c5e95576abf583bc86aa6a0ad0e1a60a7c3fdb0a8cde3b5946523d3fd22
7
- data.tar.gz: c1df6170f906e47a460abb59a52ee4054ae5434daa926c6da8062cecda9e1554227efdfb936fd5204a848436cb08af55d1a7bcffa5b8f05870c8fba7fb57513d
6
+ metadata.gz: 6794d0ac24c5fa6f0e95bd98e90cf8456f42b12c3cc8b3c0f3933f76a01912e1f62a798eddbc0076068351077b08d9e80e1071305630a256a9f27a78091071c3
7
+ data.tar.gz: 4356aa0fca5ffd090d90a39450d2d8f5dfa90286aa9d0c66e7708ded6e1669d1cfa853ea5d4dbab9dc34476c233e0f2e72b5bed759833b82f55cd7b52de561b3
data/.travis.yml CHANGED
@@ -11,6 +11,9 @@ before_install:
11
11
  - gem install bundler -v 1.12.5
12
12
  before_script:
13
13
  - bundle exec rake ditty:prep
14
+ script:
15
+ - bundle exec rake
16
+ - bundle exec rubocop --fail-level W lib views
14
17
  addons:
15
18
  code_climate:
16
19
  repo_token: 289860573c6284a8e277de86848caba84d840be49e35f3601bcd672ab40d1e35
@@ -50,22 +50,62 @@ module Ditty
50
50
  end
51
51
 
52
52
  not_found do
53
- haml :'404', locals: { title: '4 oh 4' }
53
+ respond_to do |format|
54
+ format.html do
55
+ haml :'404', locals: { title: '4 oh 4' }
56
+ end
57
+ format.json do
58
+ [404, { 'Content-Type' => 'application/json' }, [{ code: 404, errors: ['Not Found'] }.to_json]]
59
+ end
60
+ end
54
61
  end
55
62
 
56
63
  error do
57
64
  error = env['sinatra.error']
58
- haml :error, locals: { title: 'Something went wrong', error: error }
65
+ broadcast(:application_error, error)
66
+ respond_to do |format|
67
+ format.html do
68
+ haml :error, locals: { title: 'Something went wrong', error: error }
69
+ end
70
+ format.json do
71
+ [500, { 'Content-Type' => 'application/json' }, [{ code: 500, errors: ['Something went wrong'] }.to_json]]
72
+ end
73
+ end
59
74
  end
60
75
 
61
76
  error Helpers::NotAuthenticated do
62
- flash[:warning] = 'Please log in first.'
63
- redirect "#{settings.map_path}/auth/identity"
77
+ respond_to do |format|
78
+ format.html do
79
+ flash[:warning] = 'Please log in first.'
80
+ redirect "#{settings.map_path}/auth/identity"
81
+ end
82
+ format.json do
83
+ [401, { 'Content-Type' => 'application/json' }, [{ code: 401, errors: ['Not Authenticated'] }.to_json]]
84
+ end
85
+ end
64
86
  end
65
87
 
66
88
  error ::Pundit::NotAuthorizedError do
67
- flash[:warning] = 'Please log in first.'
68
- redirect "#{settings.map_path}/auth/identity"
89
+ respond_to do |format|
90
+ format.html do
91
+ flash[:warning] = 'Please log in first.'
92
+ redirect "#{settings.map_path}/auth/identity"
93
+ end
94
+ format.json do
95
+ [401, { 'Content-Type' => 'application/json' }, [{ code: 401, errors: ['Not Authorized'] }.to_json]]
96
+ end
97
+ end
98
+ end
99
+
100
+ error ::Sequel::ForeignKeyConstraintViolation do
101
+ respond_to do |format|
102
+ format.html do
103
+ haml :error, locals: { title: 'Something went wrong', error: error }
104
+ end
105
+ format.json do
106
+ [400, { 'Content-Type' => 'application/json' }, [{ code: 400, errors: ['Invalid Relation Specified'] }.to_json]]
107
+ end
108
+ end
69
109
  end
70
110
 
71
111
  before(/.*/) do
@@ -13,6 +13,10 @@ module Ditty
13
13
  set view_location: nil
14
14
  set track_actions: false
15
15
 
16
+ def read(id)
17
+ dataset.first(settings.model_class.primary_key => id)
18
+ end
19
+
16
20
  # List
17
21
  get '/', provides: %i[html json] do
18
22
  authorize settings.model_class, :list
@@ -29,8 +33,8 @@ module Ditty
29
33
  format.json do
30
34
  # TODO: Add links defined by actions (New #{heading})
31
35
  json(
32
- 'items' => list.map(&:for_json),
33
- 'page' => params[:page],
36
+ 'items' => list.all.map(&:for_json),
37
+ 'page' => (params['page'] || 1).to_i,
34
38
  'count' => list.count,
35
39
  'total' => dataset.count
36
40
  )
@@ -63,11 +67,11 @@ module Ditty
63
67
  end
64
68
  end
65
69
  format.json do
66
- headers 'Content-Type' => 'application/json'
70
+ content_type :json
67
71
  if success
68
72
  redirect "#{base_path}/#{entity.id}", 201
69
73
  else
70
- 400
74
+ [400, { errors: entity.errors }.to_json]
71
75
  end
72
76
  end
73
77
  end
@@ -75,7 +79,7 @@ module Ditty
75
79
 
76
80
  # Read
77
81
  get '/:id' do |id|
78
- entity = dataset.first(settings.model_class.primary_key => id)
82
+ entity = read(id)
79
83
  halt 404 unless entity
80
84
  authorize entity, :read
81
85
 
@@ -97,7 +101,7 @@ module Ditty
97
101
 
98
102
  # Update Form
99
103
  get '/:id/edit' do |id|
100
- entity = dataset.first(settings.model_class.primary_key => id)
104
+ entity = read(id)
101
105
  halt 404 unless entity
102
106
  authorize entity, :update
103
107
 
@@ -106,7 +110,7 @@ module Ditty
106
110
 
107
111
  # Update
108
112
  put '/:id' do |id|
109
- entity = dataset.first(settings.model_class.primary_key => id)
113
+ entity = read(id)
110
114
  halt 404 unless entity
111
115
  authorize entity, :update
112
116
 
@@ -117,6 +121,7 @@ module Ditty
117
121
  if success
118
122
  respond_to do |format|
119
123
  format.html do
124
+ # TODO: Ability to customize the return path and message?
120
125
  flash[:success] = "#{heading} Updated"
121
126
  redirect "#{base_path}/#{entity.id}"
122
127
  end
@@ -131,14 +136,15 @@ module Ditty
131
136
  haml :"#{view_location}/edit", locals: { entity: entity, title: heading(:edit) }
132
137
  end
133
138
  format.json do
134
- 400
139
+ content_type :json
140
+ [400, { errors: entity.errors }.to_json]
135
141
  end
136
142
  end
137
143
  end
138
144
  end
139
145
 
140
146
  delete '/:id' do |id|
141
- entity = dataset.first(settings.model_class.primary_key => id)
147
+ entity = read(id)
142
148
  halt 404 unless entity
143
149
  authorize entity, :delete
144
150
 
@@ -151,7 +157,7 @@ module Ditty
151
157
  redirect base_path.to_s
152
158
  end
153
159
  format.json do
154
- content_type 'application/json'
160
+ content_type :json
155
161
  headers 'Location' => '/users'
156
162
  status 204
157
163
  end
@@ -59,11 +59,11 @@ module Ditty
59
59
  respond_to do |format|
60
60
  format.html do
61
61
  flash[:success] = 'User created'
62
- redirect "/users/#{user.id}"
62
+ redirect "#{base_path}/#{user.id}"
63
63
  end
64
64
  format.json do
65
65
  headers 'Content-Type' => 'application/json'
66
- redirect "/users/#{user.id}", 201
66
+ redirect "#{base_path}/#{user.id}", 201
67
67
  end
68
68
  end
69
69
  else
@@ -87,7 +87,7 @@ module Ditty
87
87
 
88
88
  # Update
89
89
  put '/:id' do |id|
90
- entity = dataset[id.to_i]
90
+ entity = dataset.first(settings.model_class.primary_key => id)
91
91
  halt 404 unless entity
92
92
  authorize entity, :update
93
93
 
@@ -105,7 +105,7 @@ module Ditty
105
105
  respond_to do |format|
106
106
  format.html do
107
107
  flash[:success] = "#{heading} Updated"
108
- redirect "/users/#{entity.id}"
108
+ redirect back
109
109
  end
110
110
  format.json do
111
111
  content_type 'application/json'
@@ -127,7 +127,7 @@ module Ditty
127
127
  end
128
128
 
129
129
  put '/:id/identity' do |id|
130
- entity = dataset[id.to_i]
130
+ entity = dataset.first(settings.model_class.primary_key => id)
131
131
  halt 404 unless entity
132
132
  authorize entity, :update
133
133
 
@@ -151,7 +151,7 @@ module Ditty
151
151
  log_action("#{dehumanized}_update_password".to_sym) if settings.track_actions
152
152
  flash[:success] = 'Password Updated'
153
153
  redirect back
154
- elsif current_user.super_admin? && current_user.id != id
154
+ elsif current_user.super_admin? && current_user.id != id.to_i
155
155
  haml :"#{view_location}/display", locals: { entity: entity, identity: identity, title: heading }
156
156
  else
157
157
  haml :"#{view_location}/profile", locals: { entity: entity, identity: identity, title: heading }
@@ -160,7 +160,7 @@ module Ditty
160
160
 
161
161
  # Delete
162
162
  delete '/:id', provides: %i[html json] do |id|
163
- entity = dataset[id.to_i]
163
+ entity = dataset.first(settings.model_class.primary_key => id)
164
164
  halt 404 unless entity
165
165
  authorize entity, :delete
166
166
 
@@ -172,7 +172,7 @@ module Ditty
172
172
  respond_to do |format|
173
173
  format.html do
174
174
  flash[:success] = "#{heading} Deleted"
175
- redirect '/users'
175
+ redirect base_path
176
176
  end
177
177
  format.json do
178
178
  content_type 'application/json'
@@ -4,11 +4,10 @@ module Ditty
4
4
  module Helpers
5
5
  module Authentication
6
6
  def current_user
7
- if env['rack.session'].nil? || env['rack.session']['user_id'].nil?
8
- self.current_user = anonymous_user
9
- end
7
+ user_id = current_user_id
8
+ self.current_user = anonymous_user if user_id.nil?
10
9
  @users ||= Hash.new { |h, k| h[k] = User[k] }
11
- @users[env['rack.session']['user_id']]
10
+ @users[user_id]
12
11
  end
13
12
 
14
13
  def current_user=(user)
@@ -16,6 +15,11 @@ module Ditty
16
15
  env['rack.session']['user_id'] = user.id if user
17
16
  end
18
17
 
18
+ def current_user_id
19
+ return env['omniauth.auth'].uid if env['omniauth.auth']
20
+ env['rack.session']['user_id'] if env['rack.session']
21
+ end
22
+
19
23
  def authenticate
20
24
  authenticated?
21
25
  end
@@ -13,10 +13,10 @@ module Ditty
13
13
  end
14
14
 
15
15
  def list
16
- params['count'] ||= 10
17
- params['page'] ||= 1
16
+ count = params['count'] || 10
17
+ page = params['page'] || 1
18
18
 
19
- dataset.select.paginate(params['page'].to_i, params['count'].to_i)
19
+ dataset.select.paginate(page.to_i, count.to_i)
20
20
  end
21
21
 
22
22
  def heading(action = nil)
@@ -46,10 +46,14 @@ module Ditty
46
46
  haml :'partials/delete_form', locals: locals
47
47
  end
48
48
 
49
- def pagination(list, base_path)
49
+ def pagination(list, base_path, qp = {})
50
+ qs = params.clone.merge(qp)
51
+ qs.delete('captures')
50
52
  locals = {
51
- next_link: list.last_page? ? '#' : "#{base_path}?page=#{list.next_page}&count=#{list.page_size}",
52
- prev_link: list.first_page? ? '#' : "#{base_path}?page=#{list.prev_page}&count=#{list.page_size}",
53
+ first_link: "#{base_path}?#{Rack::Utils.build_query(qs.merge('page' => 1))}",
54
+ next_link: list.last_page? ? '#' : "#{base_path}?#{Rack::Utils.build_query(qs.merge('page' => list.next_page))}",
55
+ prev_link: list.first_page? ? '#' : "#{base_path}?#{Rack::Utils.build_query(qs.merge('page' => list.prev_page))}",
56
+ last_link: "#{base_path}?#{Rack::Utils.build_query(qs.merge('page' => list.page_count))}",
53
57
  base_path: base_path,
54
58
  list: list
55
59
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'wisper'
2
4
 
3
5
  module Ditty
@@ -11,7 +13,7 @@ module Ditty
11
13
  return unless args[0].is_a? Hash
12
14
  vals[:user] = args[0][:user] if args[0] && args[0].key?(:user)
13
15
  vals[:details] = args[0][:details] if args[0] && args[0].key?(:details)
14
- @mutex.synchronize { AuditLog.create vals }
16
+ @mutex.synchronize { Ditty::AuditLog.create vals }
15
17
  end
16
18
 
17
19
  def respond_to_missing?(_method, _include_private = false)
@@ -20,4 +22,4 @@ module Ditty
20
22
  end
21
23
  end
22
24
 
23
- Wisper.subscribe(Ditty::Listener.new)
25
+ Wisper.subscribe(Ditty::Listener.new) unless ENV['RACK_ENV'] == 'test'
@@ -0,0 +1,20 @@
1
+ module Ditty
2
+ module Middleware
3
+ class ErrorCatchall
4
+ attr_reader :env
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ @env = env
12
+ begin
13
+ @app.call env
14
+ rescue StandardError
15
+ [500, {}, ['Unknown Error']]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -32,13 +32,13 @@ module Ditty
32
32
  puts 'Preparing the Ditty public folder'
33
33
  Dir.mkdir 'public' unless File.exist?('public')
34
34
  ::Ditty::Components.public_folder.each do |path|
35
- FileUtils.cp_r "#{path}/.", 'public'
35
+ FileUtils.cp_r "#{path}/.", 'public' unless File.expand_path("#{path}/.").eql? File.expand_path('public')
36
36
  end
37
37
 
38
38
  puts 'Preparing the Ditty migrations folder'
39
39
  Dir.mkdir 'migrations' unless File.exist?('migrations')
40
40
  ::Ditty::Components.migrations.each do |path|
41
- FileUtils.cp_r "#{path}/.", 'migrations'
41
+ FileUtils.cp_r "#{path}/.", 'migrations' unless File.expand_path("#{path}/.").eql? File.expand_path('migrations')
42
42
  end
43
43
  puts 'Migrations added:'
44
44
  Dir.foreach('migrations').sort.each { |x| puts x if File.file?("migrations/#{x}") }
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mail'
4
+ require 'ditty/services/logger'
5
+
6
+ module Ditty
7
+ module Services
8
+ module Email
9
+ CONFIG = './config/email.yml'.freeze
10
+
11
+ class << self
12
+ def method_missing(method, *args, &block)
13
+ return super unless respond_to_missing?(method)
14
+ config!
15
+ Mail.send(method, *args, &block)
16
+ end
17
+
18
+ def respond_to_missing?(method, _include_private = false)
19
+ Mail.respond_to? method
20
+ end
21
+
22
+ def config
23
+ @config ||= symbolize_keys File.exist?(CONFIG) ? YAML.load_file(CONFIG) : default
24
+ end
25
+
26
+ private
27
+
28
+ def config!
29
+ cfg = config
30
+ Mail.defaults do
31
+ delivery_method cfg[:delivery_method].to_sym, (cfg[:options] || {})
32
+ end
33
+ end
34
+
35
+ def default
36
+ { delivery_method: :logger, logger: Ditty::Services::Logger.instance }
37
+ end
38
+
39
+ def symbolize_keys(hash)
40
+ return hash.map { |v| symbolize_keys(v) } if hash.is_a? Array
41
+ return hash unless hash.is_a? Hash
42
+ Hash[hash.map { |k, v| [k.to_sym, symbolize_keys(v)] }]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/ditty/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ditty
4
- VERSION = '0.3.1'.freeze
4
+ VERSION = '0.3.2'.freeze
5
5
  end
data/lib/ditty.rb CHANGED
@@ -33,6 +33,10 @@ module Ditty
33
33
  def inject(memo, &block)
34
34
  @mutex.synchronize { @hash.inject(memo, &block) }
35
35
  end
36
+
37
+ def each_with_object(memo, &block)
38
+ @mutex.synchronize { @hash.each_with_object(memo, &block) }
39
+ end
36
40
  end
37
41
 
38
42
  # Ripped off from Roda - https://github.com/jeremyevans/roda
@@ -68,19 +72,19 @@ module Ditty
68
72
 
69
73
  # Return a hash of controllers with their routes as keys: `{ '/users' => Ditty::Controllers::Users }`
70
74
  def self.routes
71
- components.inject({}) do |memo, comp|
75
+ rts = components.each_with_object({}) do |comp, memo|
72
76
  memo.merge! comp[1].routes if comp[1].respond_to?(:routes)
73
- memo
74
- end.compact
77
+ end
78
+ rts.compact
75
79
  end
76
80
 
77
81
  # Return an ordered list of navigation items:
78
82
  # `[{order:0, link:'/users/', text:'Users'}, {order:1, link:'/roles/', text:'Roles'}]
79
83
  def self.navigation
80
- components.inject([]) do |memo, comp|
84
+ nav = components.each_with_object([]) do |comp, memo|
81
85
  memo.concat comp[1].navigation if comp[1].respond_to?(:navigation)
82
- memo
83
- end.sort_by { |v| v[:order] }
86
+ end
87
+ nav.sort_by { |v| v[:order] }
84
88
  end
85
89
 
86
90
  def self.migrations
@@ -102,9 +106,8 @@ module Ditty
102
106
  end
103
107
 
104
108
  def self.workers
105
- components.inject([]) do |memo, comp|
109
+ components.each_with_object([]) do |comp, memo|
106
110
  memo.concat comp[1].workers if comp[1].respond_to?(:workers)
107
- memo
108
111
  end
109
112
  end
110
113
 
@@ -2,13 +2,25 @@
2
2
  - if list.pagination_record_count > 0
3
3
  %p.text-center Showing #{list.current_page_record_range} of #{list.pagination_record_count} records
4
4
  %ul.pager
5
- %li
6
- %a{href: "#{base_path}?page=1&count=#{list.page_size}"} First
7
- %li{class: ("disabled" if list.first_page?)}
8
- %a{href: prev_link} Previous
9
- %li{class: ("disabled" if list.last_page?)}
10
- %a{href: next_link} Next
11
- %li
12
- %a{href: "#{base_path}?page=#{list.page_count}&count=#{list.page_size}"} Last
5
+ - if list.first_page?
6
+ %li.disabled
7
+ %span First
8
+ %li.disabled
9
+ %span Previous
10
+ - else
11
+ %li
12
+ %a{href: first_link} First
13
+ %li
14
+ %a{href: prev_link} Previous
15
+ - if list.last_page?
16
+ %li.disabled
17
+ %span Next
18
+ %li.disabled
19
+ %span Last
20
+ - else
21
+ %li
22
+ %a{href: next_link} Next
23
+ %li
24
+ %a{href: last_link} Last
13
25
  - else
14
26
  %p.text-center No records to show
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ditty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgens du Toit
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-21 00:00:00.000000000 Z
11
+ date: 2017-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -352,6 +352,7 @@ files:
352
352
  - lib/ditty/helpers/views.rb
353
353
  - lib/ditty/helpers/wisper.rb
354
354
  - lib/ditty/listener.rb
355
+ - lib/ditty/middleware/error_catchall.rb
355
356
  - lib/ditty/models/audit_log.rb
356
357
  - lib/ditty/models/base.rb
357
358
  - lib/ditty/models/identity.rb
@@ -364,6 +365,7 @@ files:
364
365
  - lib/ditty/policies/user_policy.rb
365
366
  - lib/ditty/rake_tasks.rb
366
367
  - lib/ditty/seed.rb
368
+ - lib/ditty/services/email.rb
367
369
  - lib/ditty/services/logger.rb
368
370
  - lib/ditty/version.rb
369
371
  - migrate/20170207_base_tables.rb