ditty 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/lib/ditty/controllers/application.rb +46 -6
- data/lib/ditty/controllers/component.rb +16 -10
- data/lib/ditty/controllers/users.rb +8 -8
- data/lib/ditty/helpers/authentication.rb +8 -4
- data/lib/ditty/helpers/component.rb +3 -3
- data/lib/ditty/helpers/views.rb +7 -3
- data/lib/ditty/listener.rb +4 -2
- data/lib/ditty/middleware/error_catchall.rb +20 -0
- data/lib/ditty/rake_tasks.rb +2 -2
- data/lib/ditty/services/email.rb +47 -0
- data/lib/ditty/version.rb +1 -1
- data/lib/ditty.rb +11 -8
- data/views/partials/pager.haml +20 -8
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b443ee45c38fca3c0f69379d0631b03f1f91263
|
4
|
+
data.tar.gz: 3b26f6324b6979e1e517c6338e41cb994dce80c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
68
|
-
|
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[
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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 =
|
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
|
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 "
|
62
|
+
redirect "#{base_path}/#{user.id}"
|
63
63
|
end
|
64
64
|
format.json do
|
65
65
|
headers 'Content-Type' => 'application/json'
|
66
|
-
redirect "
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
8
|
-
|
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[
|
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']
|
17
|
-
params['page']
|
16
|
+
count = params['count'] || 10
|
17
|
+
page = params['page'] || 1
|
18
18
|
|
19
|
-
dataset.select.paginate(
|
19
|
+
dataset.select.paginate(page.to_i, count.to_i)
|
20
20
|
end
|
21
21
|
|
22
22
|
def heading(action = nil)
|
data/lib/ditty/helpers/views.rb
CHANGED
@@ -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
|
-
|
52
|
-
|
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
|
}
|
data/lib/ditty/listener.rb
CHANGED
@@ -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
|
data/lib/ditty/rake_tasks.rb
CHANGED
@@ -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
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.
|
75
|
+
rts = components.each_with_object({}) do |comp, memo|
|
72
76
|
memo.merge! comp[1].routes if comp[1].respond_to?(:routes)
|
73
|
-
|
74
|
-
|
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.
|
84
|
+
nav = components.each_with_object([]) do |comp, memo|
|
81
85
|
memo.concat comp[1].navigation if comp[1].respond_to?(:navigation)
|
82
|
-
|
83
|
-
|
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.
|
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
|
|
data/views/partials/pager.haml
CHANGED
@@ -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
|
-
|
6
|
-
%
|
7
|
-
|
8
|
-
%
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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.
|
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-
|
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
|