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 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