web47core 0.0.9 → 0.0.10
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 +4 -4
- data/Gemfile.lock +16 -4
- data/README.md +37 -33
- data/config/locales/en.yml +11 -0
- data/lib/app/models/concerns/app47_logger.rb +1 -1
- data/lib/app/models/concerns/core_system_configuration.rb +5 -1
- data/lib/app/models/concerns/email_able.rb +84 -0
- data/lib/app/models/concerns/search_able.rb +100 -0
- data/lib/app/models/concerns/standard_model.rb +2 -2
- data/lib/app/models/concerns/time_zone_able.rb +49 -0
- data/lib/app/models/notification.rb +3 -3
- data/lib/app/models/redis_configuration.rb +137 -0
- data/lib/app/models/slack_notification.rb +14 -30
- data/lib/app/models/sms_notification.rb +5 -7
- data/lib/app/models/smtp_configuration.rb +14 -89
- data/lib/web47core.rb +4 -0
- data/test/fixtures/redis/host.yml +5 -0
- data/test/fixtures/redis/options.yml +8 -0
- data/test/fixtures/redis/sentinel.yml +8 -0
- data/test/fixtures/redis/url.yml +2 -0
- data/test/models/concerns/email_able_test.rb +145 -0
- data/test/models/concerns/search_able_test.rb +80 -0
- data/test/models/concerns/standard_model_test.rb +62 -1
- data/test/models/concerns/time_zone_able_test.rb +77 -0
- data/test/models/redis_configuration_test.rb +86 -0
- data/test/models/slack_notification_test.rb +41 -0
- data/test/models/sms_notification_test.rb +69 -0
- data/test/models/smtp_configuration_test.rb +66 -0
- data/test/test_helper.rb +5 -1
- data/web47core.gemspec +7 -4
- metadata +87 -14
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Objects that can localize time to it, kicking out safe uses of a date format.
|
5
|
+
#
|
6
|
+
module TimeZoneAble
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
#
|
12
|
+
# Fields
|
13
|
+
#
|
14
|
+
field :time_zone, type: String
|
15
|
+
#
|
16
|
+
# Validations
|
17
|
+
#
|
18
|
+
validates :time_zone, inclusion: TZInfo::Timezone.all_identifiers, presence: true
|
19
|
+
#
|
20
|
+
# Callbacks
|
21
|
+
#
|
22
|
+
before_validation :default_time_zone
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Return the given time in the localized time for this object
|
28
|
+
#
|
29
|
+
def local_time(time, format = :medium, default = 'N/A')
|
30
|
+
tz = TZInfo::Timezone.get(time_zone.presence || SystemConfiguration.default_time_zone)
|
31
|
+
time.present? ? I18n.l(time.in_time_zone(tz), format: format) : default
|
32
|
+
rescue StandardError
|
33
|
+
default
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Return the updated_at if found, otherwise return the created_at. If neither are there
|
38
|
+
# then return unknown
|
39
|
+
#
|
40
|
+
def local_updated_time(obj, format = :medium, default = 'N/A')
|
41
|
+
obj.updated_at.present? ? local_time(obj.updated_at, format, default) : local_time(created_at, format, default)
|
42
|
+
rescue StandardError
|
43
|
+
default
|
44
|
+
end
|
45
|
+
|
46
|
+
def default_time_zone
|
47
|
+
self.time_zone ||= SystemConfiguration.default_time_zone
|
48
|
+
end
|
49
|
+
end
|
@@ -97,9 +97,9 @@ class Notification
|
|
97
97
|
#
|
98
98
|
# Finish processing the notification successfully
|
99
99
|
#
|
100
|
-
def finish_processing(
|
101
|
-
if
|
102
|
-
set state: STATE_INVALID, error_message:
|
100
|
+
def finish_processing(processing_message = nil)
|
101
|
+
if processing_message.present?
|
102
|
+
set state: STATE_INVALID, error_message: processing_message
|
103
103
|
else
|
104
104
|
set state: STATE_PROCESSED, error_message: ''
|
105
105
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Easily fetch the redis configuration from redis.yml in the config directory.
|
5
|
+
# There are several formats to support
|
6
|
+
#
|
7
|
+
# URL with single server
|
8
|
+
# development:
|
9
|
+
# url: 'redis://localhost:6379/0'
|
10
|
+
#
|
11
|
+
# Host/port combination for single server
|
12
|
+
# development:
|
13
|
+
# host: localhost
|
14
|
+
# port: 6379
|
15
|
+
# db: 0
|
16
|
+
#
|
17
|
+
# Sentinel
|
18
|
+
# development:
|
19
|
+
# master: production
|
20
|
+
# sentinels:
|
21
|
+
# - host1:6379
|
22
|
+
# - host2:6379
|
23
|
+
# - host3:6379
|
24
|
+
# role: master
|
25
|
+
#
|
26
|
+
# Available for all options
|
27
|
+
# connect_timeout: 0.2
|
28
|
+
# read_timeout: 0.2
|
29
|
+
# write_timeout: 0.2
|
30
|
+
# timeout: 1
|
31
|
+
#
|
32
|
+
class RedisConfiguration
|
33
|
+
#
|
34
|
+
# Methods are static
|
35
|
+
#
|
36
|
+
class << self
|
37
|
+
#
|
38
|
+
# Load the configuration using the given DB
|
39
|
+
#
|
40
|
+
# Trying first the URL, then host, then sentinel, then default method
|
41
|
+
#
|
42
|
+
def load(database = nil)
|
43
|
+
load_url(database) || load_host(database) || load_sentinel(database) || load_default(database)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
#
|
49
|
+
# Load the base set of parameters from the config file
|
50
|
+
#
|
51
|
+
def load_base(raw_config)
|
52
|
+
base = {}
|
53
|
+
%i[connect_timeout namespace read_timeout write_timeout timeout].each do |key|
|
54
|
+
base[key] = raw_config[key.to_s] unless raw_config[key.to_s].nil?
|
55
|
+
end
|
56
|
+
base
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Load a default configuration with the given database
|
61
|
+
#
|
62
|
+
def load_default(database = 0)
|
63
|
+
{ host: '127.0.0.1', port: 6_379, db: (database || 0) }
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Load the url
|
68
|
+
#
|
69
|
+
def load_url(database = 0)
|
70
|
+
raw_config = load_file
|
71
|
+
return if raw_config.nil? || raw_config['url'].nil?
|
72
|
+
|
73
|
+
config = load_base(raw_config)
|
74
|
+
config[:url] = raw_config['url']
|
75
|
+
config[:db] = (database || 0)
|
76
|
+
config
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Load the given host
|
81
|
+
#
|
82
|
+
def load_host(database = nil)
|
83
|
+
raw_config = load_file
|
84
|
+
return if raw_config.nil? || raw_config['host'].nil?
|
85
|
+
|
86
|
+
config = load_base(raw_config)
|
87
|
+
config[:host] = raw_config['host']
|
88
|
+
config[:port] = raw_config['port'] || 6_379
|
89
|
+
config[:db] = database || raw_config['db'] || 0
|
90
|
+
config
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Load the sentinel configuration
|
95
|
+
#
|
96
|
+
def load_sentinel(database = 0)
|
97
|
+
raw_config = load_file
|
98
|
+
return if raw_config.nil? || raw_config['master'].nil?
|
99
|
+
|
100
|
+
config = load_base(raw_config)
|
101
|
+
config[:url] = "redis://#{raw_config['master']}"
|
102
|
+
config[:sentinels] = raw_config['sentinels'].collect do |sentinel|
|
103
|
+
parts = sentinel.split(':')
|
104
|
+
host = parts.first
|
105
|
+
port = parts.count.eql?(2) ? parts.last : 26_379
|
106
|
+
{ host: host, port: port }
|
107
|
+
end
|
108
|
+
config[:db] = database || 0
|
109
|
+
config[:role] = raw_config['role'] || 'master'
|
110
|
+
config
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Return the config file
|
115
|
+
#
|
116
|
+
def config_file_path
|
117
|
+
File.expand_path('config/redis.yml')
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Load the config/redis.yml and return the environment name
|
122
|
+
#
|
123
|
+
def load_file
|
124
|
+
config = nil
|
125
|
+
file_path = config_file_path
|
126
|
+
if File.exist? file_path
|
127
|
+
yaml_data = YAML.load_file(file_path)
|
128
|
+
config = yaml_data[Rails.env] if yaml_data
|
129
|
+
end
|
130
|
+
|
131
|
+
config
|
132
|
+
rescue StandardError => error
|
133
|
+
puts "Error loading #{config_file_path} file", error
|
134
|
+
nil # return nothing if there is an error
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -31,38 +31,24 @@ class SlackNotification < Notification
|
|
31
31
|
# The slack username to use
|
32
32
|
field :from, type: String
|
33
33
|
|
34
|
-
def deliver_message
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
payload[:username] = from.presence || Rails.env
|
48
|
-
# Setup the delivery method for this message only.
|
49
|
-
RestClient.post(SystemConfiguration.slack_api_url, payload.to_json)
|
50
|
-
finish_processing
|
34
|
+
def deliver_message
|
35
|
+
if SystemConfiguration.slack_configured?
|
36
|
+
start_processing
|
37
|
+
payload = { text: message }
|
38
|
+
payload[:channel] = to.presence || SystemConfiguration.slack_support_channel
|
39
|
+
# Use the environment as the default, otherwise set it as the from
|
40
|
+
payload[:username] = from.presence || Rails.env
|
41
|
+
# Setup the delivery method for this message only.
|
42
|
+
RestClient.post(SystemConfiguration.slack_api_url, payload.to_json)
|
43
|
+
finish_processing
|
44
|
+
else
|
45
|
+
finish_processing 'Slack is not configured'
|
46
|
+
end
|
51
47
|
rescue StandardError => error
|
52
|
-
|
53
|
-
App47Logger.log_debug error.message
|
54
|
-
App47Logger.log_debug error.backtrace
|
55
|
-
App47Logger.log_debug '!!! Error sending SLACK notification !!!'
|
48
|
+
log_warn '!!! Error sending SLACK notification !!!', error
|
56
49
|
finish_processing error.message
|
57
50
|
end
|
58
51
|
|
59
|
-
#
|
60
|
-
# Limit the number of times the slack notification is retried
|
61
|
-
#
|
62
|
-
def max_retries
|
63
|
-
1
|
64
|
-
end
|
65
|
-
|
66
52
|
#
|
67
53
|
# Default delivery channel is email, override for sms, SMTP or other channels
|
68
54
|
#
|
@@ -74,8 +60,6 @@ class SlackNotification < Notification
|
|
74
60
|
# Convenience method to say something when we see something
|
75
61
|
#
|
76
62
|
def self.say(message, to: nil, from: nil, template: nil)
|
77
|
-
return unless SystemConfiguration.slack_configured?
|
78
|
-
|
79
63
|
notification = SlackNotification.new(to: to, from: from)
|
80
64
|
notification.message = if template.present?
|
81
65
|
notification.message_from_template template, message
|
@@ -26,9 +26,7 @@ class SmsNotification < Notification
|
|
26
26
|
#
|
27
27
|
field :sid, type: String
|
28
28
|
|
29
|
-
|
30
|
-
validates_presence_of :to
|
31
|
-
validates_presence_of :account_id
|
29
|
+
validates :to, presence: true, format: { with: /\A\+[1-9]{1}[0-9]{3,14}\z/ }
|
32
30
|
|
33
31
|
def deliver_message!
|
34
32
|
return unless SystemConfiguration.twilio_configured?
|
@@ -38,13 +36,13 @@ class SmsNotification < Notification
|
|
38
36
|
auth_token = config.twilio_auth_token
|
39
37
|
client = Twilio::REST::Client.new account_sid, auth_token
|
40
38
|
|
41
|
-
|
42
|
-
body:
|
43
|
-
to:
|
39
|
+
twilio_message = client.account.messages.create(
|
40
|
+
body: message,
|
41
|
+
to: to,
|
44
42
|
from: config.twilio_phone_number
|
45
43
|
)
|
46
44
|
# We are saved in the calling class, no reason to save again
|
47
|
-
set sid:
|
45
|
+
set sid: twilio_message.sid
|
48
46
|
end
|
49
47
|
|
50
48
|
#
|
@@ -28,7 +28,6 @@ class SmtpConfiguration
|
|
28
28
|
# Callbacks
|
29
29
|
#
|
30
30
|
before_save :update_token
|
31
|
-
#after_save :send_smtp_verification
|
32
31
|
|
33
32
|
#
|
34
33
|
# If we can use this SMTP configuration or not, it must be confirmed and
|
@@ -38,111 +37,37 @@ class SmtpConfiguration
|
|
38
37
|
confirmed? && active?
|
39
38
|
end
|
40
39
|
|
40
|
+
#
|
41
|
+
# Which fields to protect
|
42
|
+
#
|
41
43
|
def secure_fields
|
42
44
|
super + %i[password]
|
43
45
|
end
|
44
46
|
|
47
|
+
#
|
48
|
+
# Validate the token, returning true of false if valid
|
49
|
+
#
|
45
50
|
def validate_token(token)
|
46
|
-
valid =
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
51
|
+
valid = if confirmation_token.present? && confirmation_token.eql?(token)
|
52
|
+
unset(:confirmation_token, :verification_message)
|
53
|
+
true
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
54
57
|
set(confirmed: valid)
|
55
58
|
valid
|
56
59
|
end
|
57
60
|
|
58
61
|
#
|
59
62
|
# Update the token on save if we are active and the token is not already set.
|
63
|
+
#
|
60
64
|
def update_token
|
61
65
|
if active?
|
62
|
-
set(confirmation_token:
|
66
|
+
set(confirmation_token: Digest::SHA256.hexdigest("#{id}_#{account.id}")) if confirmation_token.nil?
|
63
67
|
set(confirmed: false)
|
64
68
|
set(verification_message: 'Sending SMTP verification email(s) to SMTP admins.')
|
65
69
|
else
|
66
70
|
set(verification_message: 'SMTP Configuration is not active, no verification email will be sent.')
|
67
71
|
end
|
68
72
|
end
|
69
|
-
|
70
|
-
def send_smtp_verification
|
71
|
-
if self.active?
|
72
|
-
sent_to= []
|
73
|
-
self.account.smtp_admins.each do |admin|
|
74
|
-
sent_to << admin.email if self.send_confirmation(admin)
|
75
|
-
end
|
76
|
-
if sent_to.empty?
|
77
|
-
set(verification_message: "No confirmations emails sent: (#{verification_message}).")
|
78
|
-
else
|
79
|
-
set(verification_message: "Confirmation emails sent to SMTP admins: #{sent_to.join(", ")}")
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def send_confirmation(member)
|
85
|
-
sent = false
|
86
|
-
begin
|
87
|
-
mail = Mail.new
|
88
|
-
# Set the from line
|
89
|
-
mail.from = email_address
|
90
|
-
|
91
|
-
# Set the to address
|
92
|
-
mail.to = member.email
|
93
|
-
|
94
|
-
# Set the subject line
|
95
|
-
mail.subject = 'Verify SMTP configuration change'
|
96
|
-
|
97
|
-
# set the message body and send
|
98
|
-
html_message = build_message
|
99
|
-
mail.html_part do
|
100
|
-
content_type 'text/html; charset=UTF-8'
|
101
|
-
body html_message
|
102
|
-
end
|
103
|
-
|
104
|
-
# Setup the delivery method for this message only.
|
105
|
-
if Rails.env.test?
|
106
|
-
mail.delivery_method :test
|
107
|
-
else
|
108
|
-
config = { address: self.server_name,
|
109
|
-
port: self.port,
|
110
|
-
authentication: self.authentication_method.to_sym,
|
111
|
-
enable_starttls_auto: self.ssl.eql?(true) }
|
112
|
-
config[:domain] = self.domain unless self.domain.nil? or self.domain.empty?
|
113
|
-
config[:user_name] = self.username unless self.username.nil? or self.username.empty?
|
114
|
-
config[:password] = self.password unless self.password.nil? or self.password.empty?
|
115
|
-
|
116
|
-
mail.delivery_method :smtp, config
|
117
|
-
end
|
118
|
-
|
119
|
-
# Deliver it
|
120
|
-
mail.deliver
|
121
|
-
sent = true
|
122
|
-
|
123
|
-
rescue Exception=>e
|
124
|
-
App47::Logger.log_error "Unable to send SMTP confirmation email #{e.message}"
|
125
|
-
App47::Logger.log_error e.backtrace
|
126
|
-
set(verification_message: "Unable to send verification email to #{member.email}, error: #{e.message}")
|
127
|
-
end
|
128
|
-
sent
|
129
|
-
end
|
130
|
-
|
131
|
-
def build_message
|
132
|
-
template = File.read(Rails.root.join('app', 'assets', 'templates', 'email', "verify_smtp_configuration.liquid"))
|
133
|
-
engine = Liquid::Template.parse(template)
|
134
|
-
engine.render({
|
135
|
-
'account_name' => account.name,
|
136
|
-
'confirmation_url' => "#{SystemConfiguration.webui_url}/smtp_configuration/confirm_change?token=#{confirmation_token}"
|
137
|
-
})
|
138
|
-
end
|
139
|
-
|
140
|
-
def to_yaml_properties(wrap = true)
|
141
|
-
if wrap
|
142
|
-
self.class.new.to_yaml_properties(false)
|
143
|
-
else
|
144
|
-
super()
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
73
|
end
|
data/lib/web47core.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'app/models/concerns/app47_logger'
|
2
2
|
require 'app/models/concerns/cdn_url'
|
3
|
+
require 'app/models/concerns/email_able'
|
4
|
+
require 'app/models/concerns/search_able'
|
5
|
+
require 'app/models/concerns/time_zone_able'
|
3
6
|
require 'app/models/concerns/standard_model'
|
4
7
|
require 'app/models/concerns/core_system_configuration'
|
5
8
|
require 'app/models/concerns/core_account'
|
9
|
+
require 'app/models/redis_configuration'
|
6
10
|
require 'app/models/notification'
|
7
11
|
require 'app/models/template'
|
8
12
|
require 'app/models/notification_template'
|