web47core 0.6.2 → 0.7.3

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
  SHA256:
3
- metadata.gz: e4fa89fdcd38c923f6fde1827baa6d3237f43120e9e82c25f9c13cc7817e580a
4
- data.tar.gz: d25bd53993294c31e1a6f89ce6cffe76c38bd0058411c97c2f4027418a37be43
3
+ metadata.gz: 6a0d81bc097ac74a05e76b6b3ba20f246b89ef28df5d2de1770477d1cd4f008a
4
+ data.tar.gz: fc7d0b4f63aafae7900e71b0e39b9e6b498b61eab81c093af5ecc78dbdd3b4a5
5
5
  SHA512:
6
- metadata.gz: 1b9b6f4ed7fef7b154e7622f664c2c93bfbd6e26524523befd9b764d3d1f72525b06243b91e8c3fac057230dfff5f659017db4893b123665353e994c3cee2dad
7
- data.tar.gz: ca8ecfac41f5bd5df4d766d48ad6823550d7fbbb62413c3820011edb151e59d5557edd18f4e967507a539fcd2731bd8198e5b352f3f6c546d95d376d034316e9
6
+ metadata.gz: e5cc45c8b415e32f21fa1fc4508e91705de8be29d73e561d5b6848e9dc06b286aae1fa9f88013ac86cfa913ac24f1dabad36a4257ab1256281a7ce71137f79c2
7
+ data.tar.gz: 2d7083baad1719b412c710d2e1bd1a74e9c37c7f83112ef0969a9508fdbf06431d8148060251f049480a0570d689a91b611c48338e090774e538f9e8bdbdd58d
data/README.md CHANGED
@@ -232,3 +232,7 @@ end
232
232
  #### ~/bin Directory
233
233
 
234
234
  Remove the delayed_job, delayed_job.sh and cron_server.sh files
235
+
236
+ #### Notification Templates
237
+
238
+ Move any notification templates from `~/app/assets/templates` to `~/lib/templates`
@@ -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, redis: redis_status, job_count: job_count_status, cron_job: cron_job_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 do
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
- { success: false, icon_name: 'error', icon_color: 'red-text', message: error.message }
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
- { success: true, icon_name: 'check_circle', icon_color: 'green-text', message: message }
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(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 job_count_status
67
- report_success "#{Delayed::Backend::Mongoid::Job.count} Jobs"
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 cron_job_status
76
- server = Cron::Server.primary_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
@@ -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
- case value.length
65
+ string_value = value.to_s
66
+ case string_value.length
66
67
  when 1..4
67
- "#{value.first}***********"
68
+ "#{string_value.first}***********"
68
69
  when 5..10
69
- "#{value.first}**********#{value.last}"
70
+ "#{string_value.first}**********#{string_value.last}"
70
71
  else
71
- "#{value[0..2]}**********#{value[-3..-1]}"
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
- - title t('.title', name: SystemConfiguration.environment.titleize)
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'
@@ -1,3 +1 @@
1
- - title t('.title', name: SystemConfiguration.environment.titleize)
2
- = edit_class_link_tag(SystemConfiguration, edit_stack_system_configuration_path)
3
- = render '/system_configurations/table'
1
+ = render '/system_configurations/show'
@@ -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 "#{Rails.env} Status"
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 perform
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|
@@ -18,7 +18,7 @@ module Cron
18
18
  #
19
19
  # Cycle through the collection and perform an upsert on it
20
20
  #
21
- def perform
21
+ def execute
22
22
  Web47core::Config.switchboard_able_models.each { |model| model.each(&:switchboard_upsert) }
23
23
  end
24
24
  end
@@ -9,8 +9,7 @@ module Cron
9
9
  #
10
10
  # Fetch each item and delete it if hasn't been updated in 30 days
11
11
  #
12
- def perform
13
- # Rails.cache.reconnect
12
+ def execute
14
13
  count = 0
15
14
  total = collection.count
16
15
  while count <= total
@@ -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
@@ -20,7 +20,7 @@ class AuditLog
20
20
  #
21
21
  # Validations
22
22
  #
23
- validates :action, inclusion: { in: ALL_ACTIONS }
23
+ # validates :action, inclusion: { in: ALL_ACTIONS }
24
24
 
25
25
  def self.sort_order
26
26
  [:created_at, -1]
@@ -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
- cache_key = "SystemConfiguration::#{Rails.env}"
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
- config.nil? ? SystemConfiguration.create(environment: Rails.env) : config
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.send method, *args
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, _include_private = false)
119
- SystemConfiguration.fields.include?(method_name)
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, _include_private = false)
123
- SystemConfiguration.fields.include?(method_name)
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 mailgun_configured?
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
- Rails.cache.delete "SystemConfiguration::#{Rails.env}"
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.mailgun_configured? && email_bounced?
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: 'User', optional: true
26
- belongs_to :created_by, class_name: 'User', optional: true
27
- has_many Web47core::Config.user_audit_model_log_symbol, dependent: :nullify
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
- result = yield
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
- associations = all_associations
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
- attribute_names
77
- end
78
-
79
- #
80
- # gather up the collections we care about and return them. For now, the
81
- # many to many associations are the ones that need some extra help.
82
- #
83
- def all_associations
84
- many_to_many_associations
85
- end
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.user_audit_model_log_class.create!(Web47core::Config.user_audit_model => user,
146
- model: model,
147
- action: model.audit_action,
148
- changed_values: App47Logger.clean_params(changes).to_json)
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.user_audit_model_log_class.create!(Web47core::Config.user_audit_model => user,
200
- model: self,
201
- action: action,
202
- changed_values: App47Logger.clean_params(changes).to_json)
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.user_audit_model_log_class.create!(Web47core::Config.user_audit_model => user,
304
- model: model,
305
- action: AuditLog::DELETE_ACTION,
306
- changed_values: App47Logger.clean_params(attributes).to_json)
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
- after_create :switchboard_upsert
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, &block|
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.smtp_user_name
142
- unless account.nil?
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
- default_subject_template(template_name) ||
235
- template_from_file(template_name, prefix: 'subject')
236
- return subject_from_liquid_text(subject, locals) if subject.present?
237
-
238
- subject = template_from_file(template_name, format: 'haml', prefix: 'subject')
239
- subject_from_haml_text(subject, locals) if subject.present?
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.templates.emails.find_by(name: template_name.to_s).subject
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: "Account#{delivery_channel.humanize}Template").template
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) || template_from_file(template_name)
198
- return message_from_liquid_text(template, locals) if template.present?
199
-
200
- template = template_from_file(template_name, format: 'haml')
201
- message_from_haml_text(template, locals) if template.present?
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, stringify_all(locals))
196
+ self.message = engine.render(Object.new, locals)
208
197
  end
209
198
 
210
199
  def message_from_haml_file(file_name, locals)
@@ -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
@@ -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, :user_audit_model
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
- @user_audit_model = :user
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.user_audit_model = :user
20
+ instance.audit_model = :user
21
21
  end
22
22
 
23
- def self.user_audit_model_log_class
24
- user_audit_model_log_class_name.constantize
23
+ def self.audit_model_log_class
24
+ audit_model_log_class_name.constantize
25
25
  end
26
26
 
27
- def self.user_audit_model_log_class_name
28
- user_audit_model_log_symbol.camelize
27
+ def self.audit_model_log_class_name
28
+ audit_model_log_symbol.camelize
29
29
  end
30
30
 
31
- def self.user_audit_model_log_symbol
32
- "#{instance.user_audit_model}_model_audit_log"
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
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Web47core
4
- VERSION = '0.6.2'
4
+ VERSION = '0.7.3'
5
5
  end
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.6.2
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-04-16 00:00:00.000000000 Z
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