web47core 0.6.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/app/controllers/status_controller.rb +68 -21
- data/app/helpers/core_helper.rb +6 -9
- data/app/views/stack/delayed_jobs/index.html.haml +1 -0
- data/app/views/stack/delayed_jobs/show.html.haml +1 -0
- data/app/views/stack/system_configurations/edit.html.haml +1 -5
- data/app/views/stack/system_configurations/show.html.haml +1 -3
- data/app/views/status/index.html.haml +1 -1
- data/lib/app/jobs/application_job.rb +112 -0
- data/lib/app/jobs/cron/switchboard_sync_configuration.rb +1 -2
- data/lib/app/jobs/cron/switchboard_sync_models.rb +1 -1
- data/lib/app/jobs/cron/trim_collection.rb +1 -2
- data/lib/app/jobs/cron/trim_notifications.rb +20 -0
- data/lib/app/models/audit_log.rb +1 -1
- data/lib/app/models/concerns/core_account.rb +14 -0
- data/lib/app/models/concerns/core_system_configuration.rb +18 -30
- data/lib/app/models/concerns/email_able.rb +1 -1
- data/lib/app/models/concerns/search_able.rb +14 -0
- data/lib/app/models/concerns/standard_model.rb +35 -57
- data/lib/app/models/concerns/switchboard_able.rb +2 -4
- data/lib/app/models/email_notification.rb +11 -16
- data/lib/app/models/email_template.rb +30 -0
- data/lib/app/models/notification.rb +10 -21
- data/lib/app/models/template.rb +27 -0
- data/lib/web47core/config.rb +13 -9
- data/lib/web47core/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a0d81bc097ac74a05e76b6b3ba20f246b89ef28df5d2de1770477d1cd4f008a
|
4
|
+
data.tar.gz: fc7d0b4f63aafae7900e71b0e39b9e6b498b61eab81c093af5ecc78dbdd3b4a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5cc45c8b415e32f21fa1fc4508e91705de8be29d73e561d5b6848e9dc06b286aae1fa9f88013ac86cfa913ac24f1dabad36a4257ab1256281a7ce71137f79c2
|
7
|
+
data.tar.gz: 2d7083baad1719b412c710d2e1bd1a74e9c37c7f83112ef0969a9508fdbf06431d8148060251f049480a0570d689a91b611c48338e090774e538f9e8bdbdd58d
|
data/README.md
CHANGED
@@ -4,36 +4,62 @@
|
|
4
4
|
# Return the status of the server
|
5
5
|
#
|
6
6
|
class StatusController < ActionController::Base
|
7
|
+
protect_from_forgery with: :exception
|
7
8
|
#
|
8
9
|
# Main (and only) page
|
9
10
|
#
|
10
11
|
def index
|
11
|
-
components = { mongo: mongo_status,
|
12
|
+
components = { mongo: mongo_status,
|
13
|
+
redis: redis_status,
|
14
|
+
jobs: delayed_jobs_status,
|
15
|
+
cron_job_servers: cron_job_servers_status,
|
16
|
+
workers: workers_status }
|
12
17
|
|
13
18
|
respond_to do |format|
|
14
|
-
format.html { render :index, locals: { components: components }, layout: false }
|
19
|
+
format.html { render :index, locals: { components: ui_decorators(components) }, layout: false }
|
15
20
|
format.json { render json: components.to_json }
|
16
|
-
format.text
|
17
|
-
overall = components.collect { |_key, item| item[:success] }.all?
|
18
|
-
render plain: components.flatten, status: overall ? 200 : 500
|
19
|
-
end
|
21
|
+
format.text { render plain: components.flatten, status: text_status(components) }
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
24
26
|
|
27
|
+
#
|
28
|
+
# Return the status of the key components, mongo, redis
|
29
|
+
#
|
30
|
+
def text_status(components)
|
31
|
+
components[:mongo][:success] && components[:redis][:success] ? 200 : 500
|
32
|
+
rescue StandardError
|
33
|
+
500
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Add UI decorators for HTML formatting
|
38
|
+
#
|
39
|
+
def ui_decorators(components)
|
40
|
+
components.each do |key, values|
|
41
|
+
if values[:success]
|
42
|
+
components[key][:icon_name] = 'check_circle'
|
43
|
+
components[key][:icon_color] = 'green-text'
|
44
|
+
else
|
45
|
+
components[key][:icon_name] = 'error'
|
46
|
+
components[key][:icon_color] = 'red-text'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
25
51
|
#
|
26
52
|
# Report an error for the check
|
27
53
|
#
|
28
|
-
def report_error(error)
|
29
|
-
|
54
|
+
def report_error(error, metrics = {})
|
55
|
+
metrics.merge(success: false, message: error.message)
|
30
56
|
end
|
31
57
|
|
32
58
|
#
|
33
59
|
# Report a success for the check
|
34
60
|
#
|
35
|
-
def report_success(message)
|
36
|
-
|
61
|
+
def report_success(message, metrics = {})
|
62
|
+
metrics.merge(success: true, message: message)
|
37
63
|
end
|
38
64
|
|
39
65
|
#
|
@@ -42,9 +68,9 @@ class StatusController < ActionController::Base
|
|
42
68
|
def mongo_status
|
43
69
|
raise 'Mongo not available' if SystemConfiguration.count.zero?
|
44
70
|
|
45
|
-
report_success "#{SystemConfiguration.count} Configs"
|
71
|
+
report_success "#{SystemConfiguration.count} Configs", count: SystemConfiguration.count
|
46
72
|
rescue StandardError => error
|
47
|
-
report_error
|
73
|
+
report_error error, count: 0
|
48
74
|
end
|
49
75
|
|
50
76
|
#
|
@@ -55,32 +81,53 @@ class StatusController < ActionController::Base
|
|
55
81
|
Rails.cache.write 'redis-status-check', value
|
56
82
|
raise 'Redis not available' unless value.eql?(Rails.cache.fetch('redis-status-check'))
|
57
83
|
|
58
|
-
report_success 'Redis Available'
|
84
|
+
report_success 'Redis Available', count: 1
|
59
85
|
rescue StandardError => error
|
60
|
-
report_error(error)
|
86
|
+
report_error(error, count: 0)
|
61
87
|
end
|
62
88
|
|
63
89
|
#
|
64
90
|
# Job Count
|
65
91
|
#
|
66
|
-
def
|
67
|
-
|
92
|
+
def delayed_jobs_status
|
93
|
+
count = Delayed::Backend::Mongoid::Job.count
|
94
|
+
report_success "#{count} Jobs", count: count
|
68
95
|
rescue StandardError => error
|
69
96
|
report_error(error)
|
70
97
|
end
|
71
98
|
|
72
99
|
#
|
73
|
-
# Cron job status
|
100
|
+
# Cron job primary status
|
74
101
|
#
|
75
|
-
def
|
76
|
-
server =
|
102
|
+
def cron_job_servers_status
|
103
|
+
server = cron_job_server
|
77
104
|
raise 'No primary server' if server.blank?
|
78
105
|
|
79
106
|
server_info = "#{server.host_name}(#{server.pid}), last check in #{server.last_check_in_at}"
|
80
107
|
raise "Primary Server is DEAD: #{server_info}" if server.dead?
|
81
108
|
|
82
|
-
report_success "Primary Server is alive: #{server_info}"
|
109
|
+
report_success "Primary Server is alive: #{server_info}",
|
110
|
+
primary_count: 1,
|
111
|
+
total_count: Cron::Server.count
|
83
112
|
rescue StandardError => error
|
84
|
-
report_error(error)
|
113
|
+
report_error(error, primary_count: 0, total_count: Cron::Server.count)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Desired and current worker status for servers
|
118
|
+
#
|
119
|
+
def workers_status
|
120
|
+
server = cron_job_server
|
121
|
+
raise 'No primary server' if server.blank? || server.dead?
|
122
|
+
|
123
|
+
report_success "Workers #{server.desired_server_count}/ #{server.current_server_count}",
|
124
|
+
desired_count: server.desired_server_count,
|
125
|
+
current_count: server.current_server_count
|
126
|
+
rescue StandardError => error
|
127
|
+
report_error(error, desired_count: 0, current_count: 0)
|
128
|
+
end
|
129
|
+
|
130
|
+
def cron_job_server
|
131
|
+
@cron_job_server ||= Cron::Server.primary_server
|
85
132
|
end
|
86
133
|
end
|
data/app/helpers/core_helper.rb
CHANGED
@@ -60,15 +60,16 @@ module CoreHelper
|
|
60
60
|
# 3. Length 5-10 only show the first and last character
|
61
61
|
# 4. Otherwise show the first and last three characters
|
62
62
|
def mask_value(value, default: '************')
|
63
|
-
return default if value.blank?
|
63
|
+
return default if value.blank? || !value.respond_to?(:to_s)
|
64
64
|
|
65
|
-
|
65
|
+
string_value = value.to_s
|
66
|
+
case string_value.length
|
66
67
|
when 1..4
|
67
|
-
"#{
|
68
|
+
"#{string_value.first}***********"
|
68
69
|
when 5..10
|
69
|
-
"#{
|
70
|
+
"#{string_value.first}**********#{string_value.last}"
|
70
71
|
else
|
71
|
-
"#{
|
72
|
+
"#{string_value[0..2]}**********#{string_value[-3..-1]}"
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -91,8 +92,4 @@ module CoreHelper
|
|
91
92
|
def edit_model_path(model)
|
92
93
|
model_action_path(model, :edit)
|
93
94
|
end
|
94
|
-
|
95
|
-
def current_user
|
96
|
-
User.new
|
97
|
-
end
|
98
95
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
=render '/delayed_jobs/index'
|
@@ -0,0 +1 @@
|
|
1
|
+
=render '/delayed_jobs/show'
|
@@ -1,5 +1 @@
|
|
1
|
-
|
2
|
-
.container
|
3
|
-
%form{action: stack_system_configuration_path, method: :post}
|
4
|
-
= render '/system_configurations/form', system_configuration: @system_configuration
|
5
|
-
= render '/common/form_actions', form_cancel_path: admin_system_configuration_path
|
1
|
+
= render '/system_configurations/edit'
|
@@ -7,7 +7,7 @@
|
|
7
7
|
%meta{ name: 'apple-mobile-web-app-status-bar-style', content: 'black'}
|
8
8
|
= stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css'
|
9
9
|
= stylesheet_link_tag 'https://fonts.googleapis.com/icon?family=Material+Icons'
|
10
|
-
%title
|
10
|
+
%title="#{Rails.env} Status"
|
11
11
|
%body{ style: 'background-color: #006064;'}
|
12
12
|
%header
|
13
13
|
%main
|
@@ -4,7 +4,26 @@
|
|
4
4
|
# Base application job that all jobs extend from
|
5
5
|
#
|
6
6
|
class ApplicationJob < ActiveJob::Base
|
7
|
+
include ActionView::Helpers::NumberHelper
|
8
|
+
include ActionView::Helpers::TextHelper
|
7
9
|
include App47Logger
|
10
|
+
attr_accessor :payload, :started_at
|
11
|
+
|
12
|
+
#
|
13
|
+
# Standard approach to running jobs
|
14
|
+
#
|
15
|
+
def perform(payload = {})
|
16
|
+
Rails.cache.reconnect
|
17
|
+
@started_at = Time.now.utc
|
18
|
+
@payload = payload
|
19
|
+
parse_payload
|
20
|
+
execute
|
21
|
+
rescue StandardError => error
|
22
|
+
log_error "Failed to execute job: #{self.inspect}", error
|
23
|
+
raise error
|
24
|
+
ensure
|
25
|
+
GC.start
|
26
|
+
end
|
8
27
|
|
9
28
|
#
|
10
29
|
# If this job should run in this current environment, defaults to true
|
@@ -20,4 +39,97 @@ class ApplicationJob < ActiveJob::Base
|
|
20
39
|
my_environments = valid_environments
|
21
40
|
(my_environments.empty? || my_environments.include?(Rails.env))
|
22
41
|
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Internal: Parse the payload
|
45
|
+
#
|
46
|
+
# payload - The payload from the class attribute
|
47
|
+
#
|
48
|
+
# Examples
|
49
|
+
#
|
50
|
+
# parse_payload
|
51
|
+
#
|
52
|
+
# Assigns the class attributes from the payload
|
53
|
+
#
|
54
|
+
def parse_payload
|
55
|
+
attributes = payload.is_a?(Hash) ? payload : JSON.parse(payload)
|
56
|
+
# Set the values contained in the payload
|
57
|
+
attributes.each_pair do |key, value|
|
58
|
+
method = "#{key}=".to_sym
|
59
|
+
send(method, value) if respond_to?(method)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Public: Determine the duration of the process, only reporting on the values that are
|
65
|
+
# greater than zero.
|
66
|
+
#
|
67
|
+
# Examples
|
68
|
+
#
|
69
|
+
# duration
|
70
|
+
# # => '1 day 1 hour 32 minutes 10 seconds'
|
71
|
+
# # => '1 hour 32 minutes 10 seconds'
|
72
|
+
# # => '32 minutes 10 seconds'
|
73
|
+
# # => '10 seconds'
|
74
|
+
#
|
75
|
+
# Returns the duration up until this point in time.
|
76
|
+
#
|
77
|
+
def duration
|
78
|
+
start_time = started_at.is_a?(String) ? Time.parse(started_at) : started_at
|
79
|
+
minutes, seconds = split_duration(Time.zone.now - start_time)
|
80
|
+
hours, minutes = split_duration minutes
|
81
|
+
days, hours = split_duration hours, 24
|
82
|
+
report_duration(days, hours, minutes, seconds)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
#
|
88
|
+
# Internal: Take the current value and split using the split value, return the dividend and modulus
|
89
|
+
#
|
90
|
+
# value - The value to split
|
91
|
+
# split - The split value to use
|
92
|
+
#
|
93
|
+
# Examples
|
94
|
+
#
|
95
|
+
# split_duration( 120 )
|
96
|
+
# # => [2, 0]
|
97
|
+
# split_duration( 125 )
|
98
|
+
# # => [2, 5]
|
99
|
+
# split_duration( 125 )
|
100
|
+
# # => [2, 5]
|
101
|
+
# split_duration( 48, 24 )
|
102
|
+
# # => [2, 0]
|
103
|
+
#
|
104
|
+
# Returns an arround with the first value as the dividend and the second as the modulus
|
105
|
+
#
|
106
|
+
def split_duration(value, split = 60)
|
107
|
+
[(value / split).to_i, (value % split).to_i]
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Internal: Put together the descriptive text of the duration.
|
112
|
+
#
|
113
|
+
# days - The number of days
|
114
|
+
# hours - The number of hours
|
115
|
+
# minutes - The number of minutes
|
116
|
+
# seconds - The number of seconds
|
117
|
+
#
|
118
|
+
# Examples
|
119
|
+
#
|
120
|
+
# report_duration(0, 0, 10, 1)
|
121
|
+
# # => "10 minutes 1 second"
|
122
|
+
# report_duration(1, 0, 10, 1)
|
123
|
+
# # => "1 day 0 hours 10 minutes 1 second"
|
124
|
+
#
|
125
|
+
# Returns the duplicated String.
|
126
|
+
#
|
127
|
+
def report_duration(days, hours, minutes, seconds)
|
128
|
+
dur = []
|
129
|
+
dur << pluralize(days, 'day') if days.positive?
|
130
|
+
dur << pluralize(hours, 'hour') if hours.positive? || days.positive?
|
131
|
+
dur << pluralize(minutes, 'minute') if minutes.positive? || hours.positive? || days.positive?
|
132
|
+
dur << pluralize(seconds, 'second')
|
133
|
+
dur.join(' ')
|
134
|
+
end
|
23
135
|
end
|
@@ -18,8 +18,7 @@ module Cron
|
|
18
18
|
#
|
19
19
|
# Cycle through all configuration keys
|
20
20
|
#
|
21
|
-
def
|
22
|
-
Rails.cache.reconnect
|
21
|
+
def execute
|
23
22
|
RestClient.get(switchboard_url,
|
24
23
|
ACCESS_TOKEN: SystemConfiguration.switchboard_stack_api_token,
|
25
24
|
content_type: 'application/json') do |response, _request, _result, &block|
|
@@ -20,5 +20,25 @@ module Cron
|
|
20
20
|
def collection
|
21
21
|
Notification.all
|
22
22
|
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Test if this should be archived
|
26
|
+
#
|
27
|
+
def archive?(item)
|
28
|
+
time = if item.is_a?(EmailNotification) && template?(item)
|
29
|
+
5.years.ago.utc
|
30
|
+
else
|
31
|
+
allowed_time
|
32
|
+
end
|
33
|
+
item.updated_at < time
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Determine if the notification has a template associated with it
|
38
|
+
#
|
39
|
+
def template?(item)
|
40
|
+
item.notification_template.present? ||
|
41
|
+
item[:notification_template_id].present? && Template.where(_id: item[:notification_template_id]).present?
|
42
|
+
end
|
23
43
|
end
|
24
44
|
end
|
data/lib/app/models/audit_log.rb
CHANGED
@@ -39,4 +39,18 @@ module CoreAccount
|
|
39
39
|
def fetch_smtp_configuration
|
40
40
|
smtp_configuration || build_smtp_configuration
|
41
41
|
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# return the email notifications templates, these are custom notifications sent by an admin.
|
45
|
+
#
|
46
|
+
def email_notification_templates
|
47
|
+
templates.where(_type: 'EmailNotificationTemplate')
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# return the email templates, these are email templates for actions in the system
|
52
|
+
#
|
53
|
+
def email_templates
|
54
|
+
templates.where(_type: 'EmailTemplate')
|
55
|
+
end
|
42
56
|
end
|
@@ -16,6 +16,7 @@ module CoreSystemConfiguration
|
|
16
16
|
#
|
17
17
|
def self.included(base)
|
18
18
|
base.class_eval do
|
19
|
+
attr_accessor :configuration
|
19
20
|
#
|
20
21
|
# Fields
|
21
22
|
#
|
@@ -73,27 +74,12 @@ module CoreSystemConfiguration
|
|
73
74
|
end
|
74
75
|
|
75
76
|
module ClassMethods
|
76
|
-
#
|
77
|
-
# Fetch the system configuration based on environment name. First try the rails cache
|
78
|
-
# if not there, then go to the database.
|
79
|
-
#
|
80
|
-
# Also, if an argument error is thrown, then just fetch from the database.
|
81
|
-
#
|
82
77
|
def configuration
|
83
|
-
|
84
|
-
|
85
|
-
begin
|
86
|
-
config = Rails.cache.fetch(cache_key, expires_in: 90.seconds) do
|
87
|
-
SystemConfiguration.find_by(environment: Rails.env)
|
88
|
-
end
|
89
|
-
rescue StandardError
|
90
|
-
# This seems to happen in testing relative to Country, when it does, remove
|
91
|
-
# ourselves from the cache and return the DB entry.
|
92
|
-
Rails.cache.delete cache_key
|
93
|
-
config = SystemConfiguration.find_or_create_by!(environment: Rails.env)
|
94
|
-
end
|
78
|
+
@configuration ||= SystemConfiguration.find_or_create_by!(environment: Rails.env)
|
79
|
+
end
|
95
80
|
|
96
|
-
|
81
|
+
def reset
|
82
|
+
@configuration = nil
|
97
83
|
end
|
98
84
|
|
99
85
|
def smtp_configuration
|
@@ -110,19 +96,21 @@ module CoreSystemConfiguration
|
|
110
96
|
#
|
111
97
|
# NOTE: Currently ignored Codacy issue: When using 'method_missing', fall back on 'super'
|
112
98
|
#
|
113
|
-
# rubocop:disable Style/MethodMissingSuper
|
114
99
|
def method_missing(method, *args, &_block)
|
115
|
-
configuration.
|
100
|
+
if configuration.respond_to?(method)
|
101
|
+
configuration.send(method, *args)
|
102
|
+
else
|
103
|
+
super
|
104
|
+
end
|
116
105
|
end
|
117
106
|
|
118
|
-
def respond_to?(method_name,
|
119
|
-
|
107
|
+
def respond_to?(method_name, include_private = false)
|
108
|
+
configuration.respond_to?(method_name, include_private) || super
|
120
109
|
end
|
121
110
|
|
122
|
-
def respond_to_missing?(method_name,
|
123
|
-
|
111
|
+
def respond_to_missing?(method_name, include_private = false)
|
112
|
+
configuration.respond_to?(method_name, include_private) || super
|
124
113
|
end
|
125
|
-
# rubocop:enable Style/MethodMissingSuper
|
126
114
|
end
|
127
115
|
|
128
116
|
#
|
@@ -147,7 +135,7 @@ module CoreSystemConfiguration
|
|
147
135
|
#
|
148
136
|
# Determine if mailgun is configured
|
149
137
|
#
|
150
|
-
def
|
138
|
+
def mail_gun_configured?
|
151
139
|
smtp_configured? && mailgun_api_key.present?
|
152
140
|
end
|
153
141
|
|
@@ -203,7 +191,7 @@ module CoreSystemConfiguration
|
|
203
191
|
config = SystemConfiguration.configuration
|
204
192
|
path = if config.zendesk_configured? && user.present?
|
205
193
|
time_now = Time.now.to_i
|
206
|
-
jti = "#{time_now}/#{rand(36**64).to_s(36)}"
|
194
|
+
jti = "#{time_now}/#{rand(36 ** 64).to_s(36)}"
|
207
195
|
payload = { jwt: JWT.encode({ iat: time_now, # Seconds since epoch, determine when this token is stale
|
208
196
|
jti: jti, # Unique token identifier, helps prevent replay attacks
|
209
197
|
name: user.name,
|
@@ -262,12 +250,12 @@ module CoreSystemConfiguration
|
|
262
250
|
slack_api_url.present?
|
263
251
|
end
|
264
252
|
|
265
|
-
private
|
253
|
+
# private
|
266
254
|
|
267
255
|
#
|
268
256
|
# Clear the cache when the object is saved
|
269
257
|
#
|
270
258
|
def clear_cache
|
271
|
-
|
259
|
+
SystemConfiguration.reset
|
272
260
|
end
|
273
261
|
end
|
@@ -62,7 +62,7 @@ module EmailAble
|
|
62
62
|
# Reset the bounced email
|
63
63
|
#
|
64
64
|
def reset_bounce_status
|
65
|
-
return unless SystemConfiguration.
|
65
|
+
return unless SystemConfiguration.mail_gun_configured? && email_bounced?
|
66
66
|
|
67
67
|
reset_url = "https://api.mailgun.net/v3/#{SystemConfiguration.smtp_domain}/bounces/#{CGI.escape(email)}"
|
68
68
|
RestClient.delete(reset_url, user: 'api', password: SystemConfiguration.mailgun_api_key)
|
@@ -39,6 +39,16 @@ module SearchAble
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
#
|
43
|
+
# Place holder to call to allow for work to be done before we gather up search text fields
|
44
|
+
#
|
45
|
+
def before_search_text; end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Place holder to call to allow for work to be done after we gather up search text fields
|
49
|
+
#
|
50
|
+
def after_search_text; end
|
51
|
+
|
42
52
|
#
|
43
53
|
# Internal: Update the search and sort text
|
44
54
|
#
|
@@ -47,8 +57,10 @@ module SearchAble
|
|
47
57
|
def update_search_and_sort_text
|
48
58
|
return if destroyed?
|
49
59
|
|
60
|
+
before_search_text
|
50
61
|
update_text(search_fields, :search_text)
|
51
62
|
update_text(sort_fields, :sort_text)
|
63
|
+
after_search_text
|
52
64
|
end
|
53
65
|
|
54
66
|
#
|
@@ -69,6 +81,8 @@ module SearchAble
|
|
69
81
|
value.to_s.rjust(4, '0')
|
70
82
|
when Array
|
71
83
|
value.empty? ? nil : value.join(' ').downcase
|
84
|
+
when Hash
|
85
|
+
value.inspect
|
72
86
|
else
|
73
87
|
value.to_s
|
74
88
|
end
|
@@ -15,16 +15,16 @@ module StandardModel
|
|
15
15
|
#
|
16
16
|
# Fields
|
17
17
|
#
|
18
|
-
field :last_modified_by_email
|
19
|
-
field :last_modified_by_name
|
20
|
-
field :created_by_email
|
21
|
-
field :created_by_name
|
18
|
+
field :last_modified_by_email, type: String
|
19
|
+
field :last_modified_by_name, type: String
|
20
|
+
field :created_by_email, type: String
|
21
|
+
field :created_by_name, type: String
|
22
22
|
#
|
23
23
|
# Relationships
|
24
24
|
#
|
25
|
-
belongs_to :last_modified_by, class_name:
|
26
|
-
belongs_to :created_by, class_name:
|
27
|
-
has_many Web47core::Config.
|
25
|
+
belongs_to :last_modified_by, class_name: Web47core::Config.audit_model_class_name, optional: true
|
26
|
+
belongs_to :created_by, class_name: Web47core::Config.audit_model_class_name, optional: true
|
27
|
+
has_many Web47core::Config.audit_model_log_symbol, dependent: :nullify
|
28
28
|
#
|
29
29
|
# Callbacks
|
30
30
|
#
|
@@ -42,9 +42,9 @@ module StandardModel
|
|
42
42
|
# Used by calling 'model.without_callback(*.args, &block) do'
|
43
43
|
def without_callback(*args, &_block)
|
44
44
|
skip_callback(*args)
|
45
|
-
|
45
|
+
yield
|
46
|
+
ensure
|
46
47
|
set_callback(*args)
|
47
|
-
result
|
48
48
|
end
|
49
49
|
|
50
50
|
#
|
@@ -60,10 +60,7 @@ module StandardModel
|
|
60
60
|
def allowed_param_names(filter_names = [])
|
61
61
|
# Always filter out the mongoid reserved items
|
62
62
|
filter_names += %w[created_at updated_at _type _id]
|
63
|
-
|
64
|
-
# filter out the relationship names so we don't have dups
|
65
|
-
associations.each { |association| filter_names << association.keys.first }
|
66
|
-
(field_names + associations).delete_if { |name| filter_names.include?(name) }
|
63
|
+
field_names(filter_names)
|
67
64
|
rescue StandardError
|
68
65
|
attribute_names.delete_if { |name| filter_names.include?(name) }
|
69
66
|
end
|
@@ -72,38 +69,19 @@ module StandardModel
|
|
72
69
|
# allow the model to filter out a name if they want to, meaning the model
|
73
70
|
# can return a subset of attribute names
|
74
71
|
#
|
75
|
-
def field_names
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# Return a collection of many to many assocations. We basically
|
89
|
-
# need to turn the current value returned by attribute names
|
90
|
-
#
|
91
|
-
# relationship_ids
|
92
|
-
#
|
93
|
-
# to
|
94
|
-
#
|
95
|
-
# { relationship_ids => [] }
|
96
|
-
#
|
97
|
-
# Telling the permit command to accept the value as an array of items.
|
98
|
-
#
|
99
|
-
def many_to_many_associations
|
100
|
-
associations = []
|
101
|
-
reflect_on_all_associations.each do |association|
|
102
|
-
next unless association.macro == :has_and_belongs_to_many
|
103
|
-
|
104
|
-
associations << { association.key => [] }
|
105
|
-
end
|
106
|
-
associations
|
72
|
+
def field_names(filter_names)
|
73
|
+
fields.collect do |field|
|
74
|
+
next if filter_names.include?(field[0])
|
75
|
+
|
76
|
+
case field[1].options[:type].to_s
|
77
|
+
when 'Hash'
|
78
|
+
{ field[0] => {} }
|
79
|
+
when 'Array'
|
80
|
+
{ field[0] => [] }
|
81
|
+
else
|
82
|
+
field[0]
|
83
|
+
end
|
84
|
+
end.compact
|
107
85
|
end
|
108
86
|
|
109
87
|
#
|
@@ -142,10 +120,10 @@ module StandardModel
|
|
142
120
|
# Log the audit record
|
143
121
|
#
|
144
122
|
def log_change(user, model, changes)
|
145
|
-
Web47core::Config.
|
146
|
-
|
147
|
-
|
148
|
-
|
123
|
+
Web47core::Config.audit_model_log_class.create!(Web47core::Config.audit_model => user,
|
124
|
+
model: model,
|
125
|
+
action: model.audit_action,
|
126
|
+
changed_values: App47Logger.clean_params(changes).to_json)
|
149
127
|
end
|
150
128
|
end
|
151
129
|
|
@@ -196,10 +174,10 @@ module StandardModel
|
|
196
174
|
# record a change for the object instance
|
197
175
|
#
|
198
176
|
def log_change(user, changes, action)
|
199
|
-
Web47core::Config.
|
200
|
-
|
201
|
-
|
202
|
-
|
177
|
+
Web47core::Config.audit_model_log_class.create!(Web47core::Config.audit_model => user,
|
178
|
+
model: self,
|
179
|
+
action: action,
|
180
|
+
changed_values: App47Logger.clean_params(changes).to_json)
|
203
181
|
end
|
204
182
|
|
205
183
|
def audit_action
|
@@ -300,10 +278,10 @@ module StandardModel
|
|
300
278
|
# Log the deletion, capturing the current values of the record before it is removed from the system
|
301
279
|
#
|
302
280
|
def log_deletion(user, model)
|
303
|
-
Web47core::Config.
|
304
|
-
|
305
|
-
|
306
|
-
|
281
|
+
Web47core::Config.audit_model_log_class.create!(Web47core::Config.audit_model => user,
|
282
|
+
model: model,
|
283
|
+
action: AuditLog::DELETE_ACTION,
|
284
|
+
changed_values: App47Logger.clean_params(attributes).to_json)
|
307
285
|
end
|
308
286
|
|
309
287
|
def capture_user_info
|
@@ -9,8 +9,7 @@ module SwitchboardAble
|
|
9
9
|
def self.included(base)
|
10
10
|
base.class_eval do
|
11
11
|
field :switchboard_id, type: String
|
12
|
-
|
13
|
-
# after_update :switchboard_upsert
|
12
|
+
after_save :switchboard_upsert
|
14
13
|
before_destroy :switchboard_delete
|
15
14
|
end
|
16
15
|
end
|
@@ -55,13 +54,12 @@ module SwitchboardAble
|
|
55
54
|
def switchboard_delete
|
56
55
|
return unless SystemConfiguration.switchboard_configured? && switchboard_id.present?
|
57
56
|
|
58
|
-
RestClient.delete(switchboard_url, ACCESS_TOKEN: sw_api_token) do |response, _request, _result
|
57
|
+
RestClient.delete(switchboard_url, ACCESS_TOKEN: sw_api_token) do |response, _request, _result|
|
59
58
|
case response.code
|
60
59
|
when 200
|
61
60
|
App47Logger.log_debug "Switchboard deleted #{inspect}"
|
62
61
|
else
|
63
62
|
App47Logger.log_error "Unable to delete the switchboard object #{inspect}, #{response.inspect}"
|
64
|
-
response.return!(&block)
|
65
63
|
end
|
66
64
|
end
|
67
65
|
end
|
@@ -110,6 +110,7 @@ class EmailNotification < Notification
|
|
110
110
|
mail.delivery_method :smtp, smtp_configuration
|
111
111
|
end
|
112
112
|
|
113
|
+
mail.header['X-Mailgun-Native-Send'] = 'yes' if SystemConfiguration.mail_gun_configured?
|
113
114
|
# Deliver it
|
114
115
|
mail.deliver
|
115
116
|
rescue StandardError => error
|
@@ -138,8 +139,8 @@ class EmailNotification < Notification
|
|
138
139
|
# sender address
|
139
140
|
#
|
140
141
|
def sender_address
|
141
|
-
address = SystemConfiguration.
|
142
|
-
|
142
|
+
address = SystemConfiguration.support_email
|
143
|
+
if account.present?
|
143
144
|
account_smtp = account.fetch_smtp_configuration
|
144
145
|
address = account_smtp.username if account_smtp.use?
|
145
146
|
end
|
@@ -230,23 +231,17 @@ class EmailNotification < Notification
|
|
230
231
|
end
|
231
232
|
|
232
233
|
def subject_from_template(template_name, locals)
|
233
|
-
subject = account_subject_template(template_name) ||
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
234
|
+
subject = account_subject_template(template_name) || Template.from_file(template_name, prefix: 'subject')
|
235
|
+
if subject.present?
|
236
|
+
subject_from_liquid_text(subject, locals)
|
237
|
+
else
|
238
|
+
subject = Template.from_file(template_name, format: 'haml', prefix: 'subject')
|
239
|
+
subject.present? ? subject_from_haml_text(subject, locals) : nil
|
240
|
+
end
|
240
241
|
end
|
241
242
|
|
242
243
|
def account_subject_template(template_name)
|
243
|
-
account.
|
244
|
-
rescue StandardError
|
245
|
-
nil
|
246
|
-
end
|
247
|
-
|
248
|
-
def default_subject_template(template_name)
|
249
|
-
EmailTemplate.where(account: nil, name: template_name.to_s).template
|
244
|
+
account.email_templates.find_by(name: template_name.to_s).subject
|
250
245
|
rescue StandardError
|
251
246
|
nil
|
252
247
|
end
|
@@ -7,7 +7,37 @@ class EmailTemplate < Template
|
|
7
7
|
#
|
8
8
|
field :subject, type: String
|
9
9
|
#
|
10
|
+
# Callbacks
|
11
|
+
#
|
12
|
+
before_save :htmlize_template
|
13
|
+
#
|
10
14
|
# Validations
|
11
15
|
#
|
12
16
|
validates :subject, presence: true
|
17
|
+
|
18
|
+
#
|
19
|
+
# Make sure the template is wrapped in html
|
20
|
+
def htmlize_template
|
21
|
+
if template.present? && !template.strip.start_with?("<")
|
22
|
+
self.template = "<body><pre>#{template}</pre></body>"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid_liquid_template
|
27
|
+
super && Liquid::Template.parse(self.subject).nil?
|
28
|
+
rescue Exception => error
|
29
|
+
self.errors.add(:subject, "Invalid liquid text in subject: #{error.message}")
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Copy the default from disk
|
35
|
+
#
|
36
|
+
def self.copy_default(name)
|
37
|
+
template = EmailTemplate.new
|
38
|
+
template.name = name
|
39
|
+
template.template = from_file name
|
40
|
+
template.subject = from_file name, prefix: 'subject'
|
41
|
+
template
|
42
|
+
end
|
13
43
|
end
|
@@ -158,7 +158,7 @@ class Notification
|
|
158
158
|
# If the account does exists or the template in the account does exist, we catch the error and return nil
|
159
159
|
#
|
160
160
|
def account_message_template(template_name)
|
161
|
-
account.templates.find_by(name: template_name.to_s, _type: "
|
161
|
+
account.templates.find_by(name: template_name.to_s, _type: "#{delivery_channel.humanize}Template").template
|
162
162
|
rescue StandardError
|
163
163
|
nil
|
164
164
|
end
|
@@ -172,20 +172,6 @@ class Notification
|
|
172
172
|
nil
|
173
173
|
end
|
174
174
|
|
175
|
-
#
|
176
|
-
# Retrieve the template out of the project
|
177
|
-
#
|
178
|
-
def template_from_file(template_name, format: 'liquid', prefix: nil)
|
179
|
-
file_name = [template_name, prefix, format].compact.join('.')
|
180
|
-
if File.exist?(Rails.root.join('lib', 'templates', delivery_channel, file_name))
|
181
|
-
File.open(Rails.root.join('lib', 'templates', delivery_channel, file_name))
|
182
|
-
else
|
183
|
-
File.read(File.join(__dir__, '../../lib', 'templates', delivery_channel, file_name))
|
184
|
-
end.read
|
185
|
-
rescue StandardError
|
186
|
-
nil
|
187
|
-
end
|
188
|
-
|
189
175
|
#
|
190
176
|
# Default delivery channel is email, override for sms, SMTP or other channels
|
191
177
|
#
|
@@ -194,17 +180,20 @@ class Notification
|
|
194
180
|
end
|
195
181
|
|
196
182
|
def message_from_template(template_name, locals)
|
197
|
-
template = account_message_template(template_name) ||
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
183
|
+
template = account_message_template(template_name) ||
|
184
|
+
Template.from_file(template_name, delivery_channel: delivery_channel)
|
185
|
+
if template.present?
|
186
|
+
message_from_liquid_text(template, locals)
|
187
|
+
else
|
188
|
+
template = Template.from_file(template_name, format: 'haml', delivery_channel: delivery_channel)
|
189
|
+
template.present? ? message_from_haml_text(template, locals) : nil
|
190
|
+
end
|
202
191
|
end
|
203
192
|
|
204
193
|
def message_from_haml_text(haml_text, locals)
|
205
194
|
locals[:base_url] = SystemConfiguration.base_url
|
206
195
|
engine = Haml::Engine.new(haml_text)
|
207
|
-
self.message = engine.render(Object.new,
|
196
|
+
self.message = engine.render(Object.new, locals)
|
208
197
|
end
|
209
198
|
|
210
199
|
def message_from_haml_file(file_name, locals)
|
data/lib/app/models/template.rb
CHANGED
@@ -18,4 +18,31 @@ class Template
|
|
18
18
|
validates :name, uniqueness: { scope: :account_id }
|
19
19
|
validates :name, presence: true
|
20
20
|
validates :template, presence: true
|
21
|
+
validate :valid_liquid_template
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
#
|
26
|
+
# Ensure that the template is correct from a liquid statement
|
27
|
+
#
|
28
|
+
def valid_liquid_template
|
29
|
+
Liquid::Template.parse(self.template).nil?
|
30
|
+
rescue Exception => error
|
31
|
+
self.errors.add(:template, "Invalid liquid text in template: #{error.message}")
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Retrieve the template out of the project
|
37
|
+
#
|
38
|
+
def self.from_file(template_name, format: 'liquid', prefix: nil, delivery_channel: 'email')
|
39
|
+
file_name = [template_name, prefix, format].compact.join('.')
|
40
|
+
if File.exist?(Rails.root.join('lib/templates', delivery_channel, file_name))
|
41
|
+
File.open(Rails.root.join('lib/templates', delivery_channel, file_name))
|
42
|
+
else
|
43
|
+
File.read(File.join(__dir__, '../../lib/templates', delivery_channel, file_name))
|
44
|
+
end.read
|
45
|
+
rescue StandardError
|
46
|
+
nil
|
47
|
+
end
|
21
48
|
end
|
data/lib/web47core/config.rb
CHANGED
@@ -6,30 +6,34 @@ module Web47core
|
|
6
6
|
#
|
7
7
|
class Config
|
8
8
|
include Singleton
|
9
|
-
attr_accessor :email_able_models, :switchboard_able_models, :
|
9
|
+
attr_accessor :email_able_models, :switchboard_able_models, :audit_model
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
@email_able_models = []
|
13
13
|
@switchboard_able_models = []
|
14
|
-
@
|
14
|
+
@audit_model = :user
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.reset
|
18
18
|
instance.email_able_models = []
|
19
19
|
instance.switchboard_able_models = []
|
20
|
-
instance.
|
20
|
+
instance.audit_model = :user
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.
|
24
|
-
|
23
|
+
def self.audit_model_log_class
|
24
|
+
audit_model_log_class_name.constantize
|
25
25
|
end
|
26
26
|
|
27
|
-
def self.
|
28
|
-
|
27
|
+
def self.audit_model_log_class_name
|
28
|
+
audit_model_log_symbol.camelize
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.
|
32
|
-
"#{instance.
|
31
|
+
def self.audit_model_log_symbol
|
32
|
+
"#{instance.audit_model}_model_audit_log"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.audit_model_class_name
|
36
|
+
instance.audit_model.to_s.camelize
|
33
37
|
end
|
34
38
|
|
35
39
|
#
|
data/lib/web47core/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: web47core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schroeder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -583,6 +583,8 @@ files:
|
|
583
583
|
- app/views/delayed_jobs/show.html.haml
|
584
584
|
- app/views/stack/cron/edit.html.haml
|
585
585
|
- app/views/stack/cron/index.html.haml
|
586
|
+
- app/views/stack/delayed_jobs/index.html.haml
|
587
|
+
- app/views/stack/delayed_jobs/show.html.haml
|
586
588
|
- app/views/stack/system_configurations/edit.html.haml
|
587
589
|
- app/views/stack/system_configurations/show.html.haml
|
588
590
|
- app/views/status/index.html.haml
|