super_settings 0.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +313 -0
- data/VERSION +1 -0
- data/app/helpers/super_settings/settings_helper.rb +32 -0
- data/app/views/layouts/super_settings/settings.html.erb +20 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20210414004553_create_super_settings.rb +34 -0
- data/lib/super_settings/application/api.js +88 -0
- data/lib/super_settings/application/helper.rb +119 -0
- data/lib/super_settings/application/images/edit.svg +1 -0
- data/lib/super_settings/application/images/info.svg +1 -0
- data/lib/super_settings/application/images/plus.svg +1 -0
- data/lib/super_settings/application/images/slash.svg +1 -0
- data/lib/super_settings/application/images/trash.svg +1 -0
- data/lib/super_settings/application/index.html.erb +169 -0
- data/lib/super_settings/application/layout.html.erb +22 -0
- data/lib/super_settings/application/layout_styles.css +193 -0
- data/lib/super_settings/application/scripts.js +718 -0
- data/lib/super_settings/application/styles.css +122 -0
- data/lib/super_settings/application.rb +38 -0
- data/lib/super_settings/attributes.rb +24 -0
- data/lib/super_settings/coerce.rb +66 -0
- data/lib/super_settings/configuration.rb +144 -0
- data/lib/super_settings/controller_actions.rb +81 -0
- data/lib/super_settings/encryption.rb +76 -0
- data/lib/super_settings/engine.rb +70 -0
- data/lib/super_settings/history_item.rb +26 -0
- data/lib/super_settings/local_cache.rb +306 -0
- data/lib/super_settings/rack_middleware.rb +210 -0
- data/lib/super_settings/rest_api.rb +195 -0
- data/lib/super_settings/setting.rb +599 -0
- data/lib/super_settings/storage/active_record_storage.rb +123 -0
- data/lib/super_settings/storage/http_storage.rb +279 -0
- data/lib/super_settings/storage/redis_storage.rb +293 -0
- data/lib/super_settings/storage/test_storage.rb +158 -0
- data/lib/super_settings/storage.rb +254 -0
- data/lib/super_settings/version.rb +5 -0
- data/lib/super_settings.rb +213 -0
- data/lib/tasks/super_settings.rake +9 -0
- data/super_settings.gemspec +35 -0
- metadata +113 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
#settings-table td p {
|
2
|
+
margin-top: 0;
|
3
|
+
margin-bottom: 0.5rem;
|
4
|
+
}
|
5
|
+
|
6
|
+
#settings-table td p:last-of-type {
|
7
|
+
margin-bottom: 0;
|
8
|
+
}
|
9
|
+
|
10
|
+
#settings-table input[type=text], #settings-table input[type=number], #settings-table input[type=date], #settings-table input[type=time], #settings-table textarea {
|
11
|
+
width: 100%;
|
12
|
+
}
|
13
|
+
|
14
|
+
.super-settings-edit-row {
|
15
|
+
background-color: #f2fdf2 !important;
|
16
|
+
}
|
17
|
+
|
18
|
+
#settings-table tr[data-deleted] td {
|
19
|
+
background-color: #ffd1d8 !important;
|
20
|
+
color: darkred;
|
21
|
+
text-decoration: line-through;
|
22
|
+
}
|
23
|
+
|
24
|
+
#settings-table tr[data-newrecord] .js-show-history {
|
25
|
+
display: none;
|
26
|
+
}
|
27
|
+
|
28
|
+
.super-settings-key {
|
29
|
+
overflow-wrap: break-word;
|
30
|
+
max-width: 30rem;
|
31
|
+
min-width: 15rem;
|
32
|
+
}
|
33
|
+
|
34
|
+
.super-settings-value {
|
35
|
+
overflow-wrap: break-word;
|
36
|
+
max-width: 30rem;
|
37
|
+
min-width: 15rem;
|
38
|
+
}
|
39
|
+
|
40
|
+
.super-settings-value-type {
|
41
|
+
width: 7rem;
|
42
|
+
}
|
43
|
+
|
44
|
+
.super-settings-description {
|
45
|
+
min-width: 20rem;
|
46
|
+
}
|
47
|
+
|
48
|
+
.super-settings-controls {
|
49
|
+
width: 6rem;
|
50
|
+
white-space: nowrap;
|
51
|
+
text-align: right;
|
52
|
+
text-decoration: none !important;
|
53
|
+
}
|
54
|
+
|
55
|
+
.super-settings-history-key {
|
56
|
+
font-weight: normal;
|
57
|
+
color: royalblue;
|
58
|
+
}
|
59
|
+
|
60
|
+
.super-settings-modal {
|
61
|
+
z-index: 1000000000000;
|
62
|
+
display: none;
|
63
|
+
padding-top: 5rem;
|
64
|
+
position: fixed;
|
65
|
+
left: 0;
|
66
|
+
top: 0;
|
67
|
+
width: 100%;
|
68
|
+
height: 100%;
|
69
|
+
overflow: auto;
|
70
|
+
background-color: rgba(0,0,0,0.4);
|
71
|
+
}
|
72
|
+
|
73
|
+
.super-settings-modal-dialog {
|
74
|
+
margin: auto;
|
75
|
+
background-color: #fff;
|
76
|
+
position: relative;
|
77
|
+
outline: 0;
|
78
|
+
padding: 2em;
|
79
|
+
box-shadow: 3px 3px 7px 3px rgba(0, 0, 0, 0.6);
|
80
|
+
border-radius: 4px;
|
81
|
+
width: 80%;
|
82
|
+
height: 80%;
|
83
|
+
}
|
84
|
+
|
85
|
+
.super-settings-modal-content {
|
86
|
+
height: 100%;
|
87
|
+
overflow: auto;
|
88
|
+
}
|
89
|
+
|
90
|
+
.super-settings-modal-close {
|
91
|
+
display: block;
|
92
|
+
position: absolute;
|
93
|
+
top: 0;
|
94
|
+
right: 0;
|
95
|
+
border: 0;
|
96
|
+
padding: 1ex;
|
97
|
+
background-color: inherit;
|
98
|
+
font-size: 1.5rem;
|
99
|
+
font-weight: 500;
|
100
|
+
}
|
101
|
+
|
102
|
+
.super-settings-sr-only {
|
103
|
+
position: absolute;
|
104
|
+
width: 1px;
|
105
|
+
height: 1px;
|
106
|
+
padding: 0;
|
107
|
+
margin: -1px;
|
108
|
+
overflow: hidden;
|
109
|
+
clip: rect(0,0,0,0);
|
110
|
+
border: 0;
|
111
|
+
}
|
112
|
+
|
113
|
+
.super-settings-text-nowrap {
|
114
|
+
white-space: nowrap !important;
|
115
|
+
}
|
116
|
+
|
117
|
+
.super-settings-sticky-top {
|
118
|
+
position: sticky;
|
119
|
+
top: 0;
|
120
|
+
padding: 1rem 0;
|
121
|
+
background-color: white;
|
122
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "application/helper"
|
4
|
+
|
5
|
+
module SuperSettings
|
6
|
+
# Simple class for rendering ERB templates for the HTML application.
|
7
|
+
class Application
|
8
|
+
include Helper
|
9
|
+
|
10
|
+
# @param layout [String, Symbol] path to an ERB template to use as the layout around the application UI. You can
|
11
|
+
# pass the symbol `:default` to use the default layout that ships with the gem.
|
12
|
+
# @param add_to_head [String] HTML code to add to the <head> element on the page.
|
13
|
+
def initialize(layout = nil, add_to_head = nil)
|
14
|
+
if layout
|
15
|
+
layout = File.expand_path(File.join("application", "layout.html.erb"), __dir__) if layout == :default
|
16
|
+
@layout = ERB.new(File.read(layout))
|
17
|
+
@add_to_head = add_to_head
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Render the specified ERB file in the lib/application directory distributed with the gem.
|
22
|
+
def render(erb_file)
|
23
|
+
template = ERB.new(File.read(File.expand_path(File.join("application", erb_file), __dir__)))
|
24
|
+
html = template.result(binding)
|
25
|
+
if @layout
|
26
|
+
render_layout { html }
|
27
|
+
else
|
28
|
+
html
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def render_layout
|
35
|
+
@layout.result(binding)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Interface to expose mass setting attributes on an object. Setting attributes with a
|
5
|
+
# hash will simply call the attribute writers for each key in the hash.
|
6
|
+
module Attributes
|
7
|
+
class UnknownAttributeError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(attributes = nil)
|
11
|
+
self.attributes = attributes if attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def attributes=(values)
|
15
|
+
values.each do |name, value|
|
16
|
+
if respond_to?("#{name}=", true)
|
17
|
+
send("#{name}=", value)
|
18
|
+
else
|
19
|
+
raise UnknownAttributeError.new("unknown attribute #{name.to_s.inspect} for #{self.class}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Utility functions for coercing values to other data types.
|
5
|
+
class Coerce
|
6
|
+
# rubocop:disable Lint/BooleanSymbol
|
7
|
+
FALSE_VALUES = [
|
8
|
+
false, 0,
|
9
|
+
"0", :"0",
|
10
|
+
"f", :f,
|
11
|
+
"F", :F,
|
12
|
+
"false", :false,
|
13
|
+
"FALSE", :FALSE,
|
14
|
+
"off", :off,
|
15
|
+
"OFF", :OFF
|
16
|
+
].to_set.freeze
|
17
|
+
# rubocop:enable Lint/BooleanSymbol
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Cast variations of booleans (i.e. "true", "false", 1, 0, etc.) to actual boolean objects.
|
21
|
+
# @param value [Object]
|
22
|
+
# @return [Boolean]
|
23
|
+
def boolean(value)
|
24
|
+
if value == false
|
25
|
+
false
|
26
|
+
elsif blank?(value)
|
27
|
+
nil
|
28
|
+
else
|
29
|
+
!FALSE_VALUES.include?(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Cast a value to a Time object.
|
34
|
+
def time(value)
|
35
|
+
value = nil if value.nil? || value.to_s.empty?
|
36
|
+
return nil if value.nil?
|
37
|
+
time = if value.is_a?(Numeric)
|
38
|
+
Time.at(value)
|
39
|
+
elsif value.respond_to?(:to_time)
|
40
|
+
value.to_time
|
41
|
+
else
|
42
|
+
Time.parse(value.to_s)
|
43
|
+
end
|
44
|
+
if time.respond_to?(:in_time_zone) && Time.respond_to?(:zone)
|
45
|
+
time = time.in_time_zone(Time.zone)
|
46
|
+
end
|
47
|
+
time
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return true if the value is nil or empty.
|
51
|
+
def blank?(value)
|
52
|
+
return true if value.nil?
|
53
|
+
if value.respond_to?(:empty?)
|
54
|
+
value.empty?
|
55
|
+
else
|
56
|
+
value.to_s.empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return true if the value is not nil and not empty.
|
61
|
+
def present?(value)
|
62
|
+
!blank?(value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module SuperSettings
|
6
|
+
# Configuration for the gem when run as a Rails engine. Default values and behaviors
|
7
|
+
# on the controller and model can be overridden with the configuration.
|
8
|
+
#
|
9
|
+
# The configuration is a singleton instance.
|
10
|
+
class Configuration
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
# Configuration for the controller.
|
14
|
+
class Controller
|
15
|
+
# @api private
|
16
|
+
attr_reader :enhancement
|
17
|
+
|
18
|
+
# Superclass for the controller. This should normally be set to one of your existing
|
19
|
+
# base controller classes since these probably have authentication methods, etc. defined
|
20
|
+
# on them. If this is not defined, the superclass will be `SuperSettings::ApplicationController`.
|
21
|
+
# It can be set to either a class or a class name. Setting to a class name is preferrable
|
22
|
+
# since it will be compatible with class reloading in a development environment.
|
23
|
+
attr_writer :superclass
|
24
|
+
|
25
|
+
def superclass
|
26
|
+
if @superclass.is_a?(String)
|
27
|
+
@superclass.constantize
|
28
|
+
else
|
29
|
+
@superclass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Optinal name of the application displayed in the view.
|
34
|
+
attr_accessor :application_name
|
35
|
+
|
36
|
+
# Optional mage URL for the application logo.
|
37
|
+
attr_accessor :application_logo
|
38
|
+
|
39
|
+
# Optional URL for a link back to the rest of the application.
|
40
|
+
attr_accessor :application_link
|
41
|
+
|
42
|
+
# Javascript to inject into the settings application HTML page. This can be used, for example,
|
43
|
+
# to set authorization credentials stored client side to access the settings API.
|
44
|
+
attr_accessor :javascript
|
45
|
+
|
46
|
+
# Enhance the controller. You can define methods or call controller class methods like
|
47
|
+
# `before_action`, etc. in the block. These will be applied to the engine controller.
|
48
|
+
# This is essentially the same a monkeypatching the controller class.
|
49
|
+
def enhance(&block)
|
50
|
+
@enhancement = block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Define how the `changed_by` attibute on the setting history will be filled from the controller.
|
54
|
+
# The block will be evaluated in the context of the controller when the settings are changed.
|
55
|
+
# The value returned by the block will be stored in the changed_by attribute. For example, if
|
56
|
+
# your base controller class defines a method `current_user` and you'd like the name to be stored
|
57
|
+
# in the history, you could call `define_changed_by { current_user.name }`
|
58
|
+
def define_changed_by(&block)
|
59
|
+
@changed_by_block = block
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return the value of `define_changed_by` block.
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def changed_by(controller)
|
66
|
+
if defined?(@changed_by_block) && @changed_by_block
|
67
|
+
controller.instance_eval(&@changed_by_block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Configuration for the models.
|
73
|
+
class Model
|
74
|
+
# Specify the cache implementation to use for caching the last updated timestamp for reloading
|
75
|
+
# changed records. Defaults to `Rails.cache`
|
76
|
+
attr_accessor :cache
|
77
|
+
|
78
|
+
attr_writer :storage
|
79
|
+
|
80
|
+
# Specify the storage engine to use for persisting settings. The value can either be specified
|
81
|
+
# as a full class name or an underscored class name for a storage classed defined in the
|
82
|
+
# `SuperSettings::Storage` namespace. The default storage engine is `SuperSettings::Storage::ActiveRecord`.
|
83
|
+
def storage
|
84
|
+
if defined?(@storage) && @storage
|
85
|
+
@storage
|
86
|
+
else
|
87
|
+
:active_record
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Class]
|
92
|
+
# @api private
|
93
|
+
def storage_class
|
94
|
+
if storage.is_a?(Class)
|
95
|
+
storage
|
96
|
+
else
|
97
|
+
class_name = storage.to_s.camelize
|
98
|
+
if Storage.const_defined?("#{class_name}Storage")
|
99
|
+
Storage.const_get("#{class_name}Storage")
|
100
|
+
else
|
101
|
+
class_name.constantize
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the model specific configuration object.
|
108
|
+
attr_reader :model
|
109
|
+
|
110
|
+
# Return the controller specific configuration object.
|
111
|
+
attr_reader :controller
|
112
|
+
|
113
|
+
# Set the secret used for encrypting secret settings. Defaults to the value of the
|
114
|
+
# SUPER_SETTINGS_SECRET environment variable. An array can be provided if you need to
|
115
|
+
# roll the secret with the first value being the current one.
|
116
|
+
attr_accessor :secret
|
117
|
+
|
118
|
+
# Set the number of seconds that settings will be cached locally before the database
|
119
|
+
# is checked for updates. Defaults to 5 seconds.
|
120
|
+
attr_accessor :refresh_interval
|
121
|
+
|
122
|
+
def initialize
|
123
|
+
@model = Model.new
|
124
|
+
@controller = Controller.new
|
125
|
+
end
|
126
|
+
|
127
|
+
# Defer the execution of a block that will be yielded to with the config object. This
|
128
|
+
# is needed in a Rails environment during initialization so that all the frameworks can
|
129
|
+
# load before loading the settings.
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
def defer(&block)
|
133
|
+
@block = block
|
134
|
+
end
|
135
|
+
|
136
|
+
# Call the block deferred during initialization.
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def call
|
140
|
+
@block&.call(self)
|
141
|
+
@block = nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
module SuperSettings
|
6
|
+
# Module used to build the SuperSettings::SettingsController for Rails applications.
|
7
|
+
# This controller is defined at runtime since it is assumed that the superclass will
|
8
|
+
# be one of the application's own base controller classes since the application will
|
9
|
+
# want to define authentication and authorization criteria.
|
10
|
+
#
|
11
|
+
# The controller is built by extending the class defined by the Configuration object and
|
12
|
+
# then mixing in this module.
|
13
|
+
module ControllerActions
|
14
|
+
def self.included(base)
|
15
|
+
base.layout "super_settings/settings"
|
16
|
+
base.helper SettingsHelper
|
17
|
+
base.protect_from_forgery with: :exception, if: :protect_from_forgery?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Render the HTML application for managing settings.
|
21
|
+
def root
|
22
|
+
html = SuperSettings::Application.new.render("index.html.erb")
|
23
|
+
render html: html.html_safe, layout: true
|
24
|
+
end
|
25
|
+
|
26
|
+
# API endpoint for getting active settings. See SuperSettings::RestAPI for details.
|
27
|
+
def index
|
28
|
+
render json: SuperSettings::RestAPI.index
|
29
|
+
end
|
30
|
+
|
31
|
+
# API endpoint for getting a setting. See SuperSettings::RestAPI for details.
|
32
|
+
def show
|
33
|
+
setting = SuperSettings::RestAPI.show(params[:key])
|
34
|
+
if setting
|
35
|
+
render json: setting
|
36
|
+
else
|
37
|
+
render json: nil, status: 404
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# API endpoint for updating settings. See SuperSettings::RestAPI for details.
|
42
|
+
def update
|
43
|
+
changed_by = Configuration.instance.controller.changed_by(self)
|
44
|
+
result = SuperSettings::RestAPI.update(params[:settings], changed_by)
|
45
|
+
if result[:success]
|
46
|
+
render json: result
|
47
|
+
else
|
48
|
+
render json: result, status: 422
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# API endpoint for getting the history of a setting. See SuperSettings::RestAPI for details.
|
53
|
+
def history
|
54
|
+
setting_history = SuperSettings::RestAPI.history(params[:key], offset: params[:offset], limit: params[:limit])
|
55
|
+
if setting_history
|
56
|
+
render json: setting_history
|
57
|
+
else
|
58
|
+
render json: nil, status: 404
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# API endpoint for getting the last time a setting was changed. See SuperSettings::RestAPI for details.
|
63
|
+
def last_updated_at
|
64
|
+
render json: SuperSettings::RestAPI.last_updated_at
|
65
|
+
end
|
66
|
+
|
67
|
+
# API endpoint for getting settings that have changed since specified time. See SuperSettings::RestAPI for details.
|
68
|
+
def updated_since
|
69
|
+
render json: SuperSettings::RestAPI.updated_since(params[:time])
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
# Return true if CSRF protection needs to be enabled for the request.
|
75
|
+
# By default it is only enabled on stateful requests that include Basic authorization
|
76
|
+
# or cookies in the request so that stateless REST API calls are allowed.
|
77
|
+
def protect_from_forgery?
|
78
|
+
request.cookies.present? || request.authorization.to_s.split(" ", 2).first&.match?(/\ABasic/i)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
module Encryption
|
5
|
+
SALT = "0c54a781"
|
6
|
+
private_constant :SALT
|
7
|
+
|
8
|
+
# Error thrown when the secret is invalid
|
9
|
+
class InvalidSecretError < StandardError
|
10
|
+
def initialize
|
11
|
+
super("Cannot decrypt. Invalid secret provided.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Set the secret key used for encrypting secret values. If this is not set,
|
17
|
+
# the value will be loaded from the `SUPER_SETTINGS_SECRET` environment
|
18
|
+
# variable. If that value is not set, arguments will not be encrypted.
|
19
|
+
#
|
20
|
+
# You can set multiple secrets by passing an array if you need to roll your secrets.
|
21
|
+
# The left most value in the array will be used as the encryption secret, but
|
22
|
+
# all the values will be tried when decrypting. That way if you have existing keys
|
23
|
+
# that were encrypted with a different secret, you can still make it available
|
24
|
+
# when decrypting. If you are using the environment variable, separate the keys
|
25
|
+
# with spaces.
|
26
|
+
#
|
27
|
+
# @param value [String] One or more secrets to use for encrypting arguments.
|
28
|
+
# @return [void]
|
29
|
+
def secret=(value)
|
30
|
+
@encryptors = make_encryptors(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Encrypt a value for use with secret settings.
|
34
|
+
# @api private
|
35
|
+
def encrypt(value)
|
36
|
+
return nil if Coerce.blank?(value)
|
37
|
+
encryptor = encryptors.first
|
38
|
+
return value if encryptor.nil?
|
39
|
+
encryptor.encrypt(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Decrypt a value for use with secret settings.
|
43
|
+
# @api private
|
44
|
+
def decrypt(value)
|
45
|
+
return nil if Coerce.blank?(value)
|
46
|
+
return value if encryptors.empty? || encryptors == [nil]
|
47
|
+
encryptors.each do |encryptor|
|
48
|
+
begin
|
49
|
+
return encryptor.decrypt(value) if encryptor
|
50
|
+
rescue OpenSSL::Cipher::CipherError
|
51
|
+
# Not the right key, try the next one
|
52
|
+
end
|
53
|
+
end
|
54
|
+
raise InvalidSecretError
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] true if the value is encrypted in the storage engine.
|
58
|
+
def encrypted?(value)
|
59
|
+
SecretKeys::Encryptor.encrypted?(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def encryptors
|
65
|
+
if !defined?(@encryptors) || @encryptors.empty?
|
66
|
+
@encryptors = make_encryptors(ENV["SUPER_SETTINGS_SECRET"].to_s.split)
|
67
|
+
end
|
68
|
+
@encryptors
|
69
|
+
end
|
70
|
+
|
71
|
+
def make_encryptors(secrets)
|
72
|
+
Array(secrets).map { |val| val.nil? ? nil : SecretKeys::Encryptor.from_password(val, SALT) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Engine that is loaded in a Rails environment. The engine will take care of applying any
|
5
|
+
# settings overriding behavior in the Configuration as well as eager loading the settings
|
6
|
+
# into memory.
|
7
|
+
class Engine < Rails::Engine
|
8
|
+
isolate_namespace ::SuperSettings
|
9
|
+
|
10
|
+
config.after_initialize do
|
11
|
+
# Call the deferred initialization block.
|
12
|
+
configuration = Configuration.instance
|
13
|
+
configuration.call
|
14
|
+
|
15
|
+
SuperSettings.refresh_interval = configuration.refresh_interval unless configuration.refresh_interval.nil?
|
16
|
+
|
17
|
+
reloader = if defined?(Rails.application.reloader.to_prepare)
|
18
|
+
Rails.application.reloader
|
19
|
+
elsif defined?(ActiveSupport::Reloader.to_prepare)
|
20
|
+
ActiveSupport::Reloader
|
21
|
+
elsif defined?(ActionDispatch::Reloader.to_prepare)
|
22
|
+
ActionDispatch::Reloader
|
23
|
+
end
|
24
|
+
|
25
|
+
create_controller = lambda do
|
26
|
+
klass = Class.new(configuration.controller.superclass || ::ApplicationController)
|
27
|
+
if defined?(SuperSettings::SettingsController)
|
28
|
+
SuperSettings.send(:remove_const, :SettingsController)
|
29
|
+
end
|
30
|
+
SuperSettings.const_set(:SettingsController, klass)
|
31
|
+
klass.include(ControllerActions)
|
32
|
+
if configuration.controller.enhancement
|
33
|
+
klass.class_eval(&configuration.controller.enhancement)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Setup the controller.
|
38
|
+
ActiveSupport.on_load(:action_controller) do
|
39
|
+
create_controller.call
|
40
|
+
if reloader && !Rails.configuration.cache_classes
|
41
|
+
reloader.to_prepare(&create_controller)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
model_load_block = proc do
|
46
|
+
Setting.cache = (configuration.model.cache || Rails.cache)
|
47
|
+
Setting.storage = configuration.model.storage_class
|
48
|
+
|
49
|
+
if configuration.secret.present?
|
50
|
+
SuperSettings.secret = configuration.secret
|
51
|
+
configuration.secret = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
if !SuperSettings.loaded?
|
55
|
+
begin
|
56
|
+
SuperSettings.load_settings
|
57
|
+
rescue => e
|
58
|
+
Rails.logger&.warn(e)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if configuration.model.storage.to_s == "active_record"
|
64
|
+
ActiveSupport.on_load(:active_record, &model_load_block)
|
65
|
+
else
|
66
|
+
model_load_block.call
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Model for each item in a setting's history. When a setting is changed, the system
|
5
|
+
# will track the value it is changed to, who it was changed by, and when.
|
6
|
+
class HistoryItem
|
7
|
+
include Attributes
|
8
|
+
|
9
|
+
attr_accessor :key, :value, :changed_by, :created_at
|
10
|
+
attr_writer :deleted
|
11
|
+
|
12
|
+
def deleted?
|
13
|
+
# Stupid strict mode...
|
14
|
+
!!(defined?(@deleted) && @deleted)
|
15
|
+
end
|
16
|
+
|
17
|
+
# The method could be overriden to change how the changed_by attribute is displayed.
|
18
|
+
# For instance, you could store a user id in the changed_by column and add an association
|
19
|
+
# on this model `belongs_to :user, class_name: "User", foreign_key: :changed_by` and then
|
20
|
+
# define this method as `user.name`.
|
21
|
+
# @return [String]
|
22
|
+
def changed_by_display
|
23
|
+
changed_by
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|