2014-03-16 02:08:15 +01:00
|
|
|
# Foodcoop-specific configuration.
|
|
|
|
#
|
|
|
|
# This is loaded from +config/app_config.yml+, which contains a root
|
|
|
|
# key for each environment (plus an optional +defaults+ key). When using
|
|
|
|
# the multicoops feature (+multicoops+ is set to +true+ for the environment),
|
|
|
|
# each foodcoop has its own key.
|
|
|
|
#
|
|
|
|
# In addition to the configuration file, values can be overridden in the database
|
|
|
|
# using {RailsSettings::CachedSettings} as +foodcoop.<foodcoop_scope>.**+.
|
|
|
|
#
|
2014-06-25 16:21:36 +02:00
|
|
|
# Some values may not be set in the database (e.g. the database connection to
|
|
|
|
# sharedlists, or +default_scope+), these are defined as children of the
|
|
|
|
# +protected+ key. The default contains a sensible list, but you can modify
|
|
|
|
# that. Here's an almost minimal example:
|
|
|
|
#
|
|
|
|
# default:
|
|
|
|
# default_scope: f
|
|
|
|
# host: order.foodstuff.test # hostname for urls in emails
|
|
|
|
#
|
|
|
|
# name: Fairy Foodstuff # the name of our foodcoop
|
|
|
|
# contact:
|
|
|
|
# # ...
|
|
|
|
# email: fairy@foodstuff.test # general contact email address
|
|
|
|
#
|
|
|
|
# price_markup: 6 # foodcoop margin
|
|
|
|
#
|
|
|
|
# protected:
|
|
|
|
# shared_lists: false # allow database connection override
|
|
|
|
# use_messages: true # foodcoops can't disable the use of messages
|
|
|
|
#
|
2014-06-27 09:07:47 +02:00
|
|
|
# When you like to whitelist protected attributes, define an entry +all: true+,
|
|
|
|
# then you can whitelist specific attributes setting them to +false+.
|
|
|
|
#
|
2012-08-24 19:52:38 +02:00
|
|
|
class FoodsoftConfig
|
2014-03-16 02:08:15 +01:00
|
|
|
# @!attribute scope
|
|
|
|
# Returns the current foodcoop scope for the multicoops feature, otherwise
|
|
|
|
# the value of the foodcoop configuration key +default_scope+ is used.
|
|
|
|
# @return [String] The current foodcoop scope.
|
|
|
|
mattr_accessor :scope
|
|
|
|
# @!attribute config
|
|
|
|
# Returns a {ActiveSupport::HashWithIndifferentAccess Hash} with the current
|
|
|
|
# scope's configuration from the configuration file. Note that this does not
|
|
|
|
# include values that were changed in the database.
|
|
|
|
# @return [ActiveSupport::HashWithIndifferentAccess] Current configuration from configuration file.
|
|
|
|
mattr_accessor :config
|
|
|
|
|
2023-01-06 16:27:41 +01:00
|
|
|
mattr_accessor :default_config
|
|
|
|
|
2014-03-16 02:08:15 +01:00
|
|
|
# Configuration file location.
|
|
|
|
# Taken from environment variable +FOODSOFT_APP_CONFIG+,
|
|
|
|
# or else +config/app_config.yml+.
|
2022-05-27 21:57:06 +02:00
|
|
|
APP_CONFIG_FILE = ENV.fetch('FOODSOFT_APP_CONFIG', 'config/app_config.yml')
|
2014-03-16 02:08:15 +01:00
|
|
|
# Loaded configuration
|
2014-09-11 16:40:09 +02:00
|
|
|
APP_CONFIG = ActiveSupport::HashWithIndifferentAccess.new
|
2012-08-24 19:52:38 +02:00
|
|
|
|
2021-02-05 16:19:05 +01:00
|
|
|
# distribution strategy config values enum
|
|
|
|
module DistributionStrategy
|
|
|
|
FIRST_ORDER_FIRST_SERVE = 'first_order_first_serve'
|
|
|
|
NO_AUTOMATIC_DISTRIBUTION = 'no_automatic_distribution'
|
|
|
|
end
|
|
|
|
|
2012-08-24 19:52:38 +02:00
|
|
|
class << self
|
2014-09-11 16:40:09 +02:00
|
|
|
# Load and initialize foodcoop configuration file.
|
|
|
|
# @param filename [String] Override configuration file
|
|
|
|
def init(filename = APP_CONFIG_FILE)
|
|
|
|
Rails.logger.info "Loading app configuration from #{APP_CONFIG_FILE}"
|
2017-03-04 12:08:15 +01:00
|
|
|
APP_CONFIG.clear.merge! YAML.load(ERB.new(File.read(File.expand_path(filename, Rails.root))).result)
|
2014-06-23 10:10:21 +02:00
|
|
|
# Gather program-default configuration
|
|
|
|
self.default_config = get_default_config
|
2012-08-24 19:52:38 +02:00
|
|
|
# Load initial config from development or production
|
|
|
|
set_config Rails.env
|
|
|
|
# Overwrite scope to have a better namescope than 'production'
|
2023-05-12 13:01:12 +02:00
|
|
|
self.scope = config[:default_scope] or raise 'No default_scope is set'
|
2013-09-20 22:40:13 +02:00
|
|
|
# Set defaults for backward-compatibility
|
|
|
|
set_missing
|
2021-02-26 15:10:22 +01:00
|
|
|
# Make sure relevant configuration is applied, also in single coops mode,
|
|
|
|
# where select_foodcoop is not called in every request.
|
|
|
|
setup_mailing
|
2012-08-24 19:52:38 +02:00
|
|
|
end
|
|
|
|
|
2019-10-28 21:04:59 +01:00
|
|
|
def init_mailing
|
2023-05-12 13:01:12 +02:00
|
|
|
%i[protocol host port script_name].each do |k|
|
2019-10-28 21:04:59 +01:00
|
|
|
ActionMailer::Base.default_url_options[k] = self[k] if self[k]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-16 02:08:15 +01:00
|
|
|
# Set config and database connection for specific foodcoop.
|
|
|
|
#
|
|
|
|
# Only needed in multi coop mode.
|
|
|
|
# @param foodcoop [String, Symbol] Foodcoop to select.
|
2012-08-24 19:52:38 +02:00
|
|
|
def select_foodcoop(foodcoop)
|
|
|
|
set_config foodcoop
|
|
|
|
setup_database
|
2017-08-17 23:49:34 +02:00
|
|
|
setup_mailing
|
2012-08-24 19:52:38 +02:00
|
|
|
end
|
|
|
|
|
2017-08-10 23:34:56 +02:00
|
|
|
def select_default_foodcoop
|
|
|
|
select_foodcoop config[:default_scope]
|
|
|
|
end
|
|
|
|
|
2016-02-26 14:42:16 +01:00
|
|
|
def select_multifoodcoop(foodcoop)
|
|
|
|
select_foodcoop foodcoop if config[:multi_coop_install]
|
|
|
|
end
|
|
|
|
|
2014-03-16 02:08:15 +01:00
|
|
|
# Return configuration value for the currently selected foodcoop.
|
|
|
|
#
|
|
|
|
# First tries to read configuration from the database (cached),
|
|
|
|
# then from the configuration files.
|
|
|
|
#
|
|
|
|
# FoodsoftConfig[:name] # => 'FC Test'
|
|
|
|
#
|
2014-06-21 12:52:50 +02:00
|
|
|
# To avoid errors when the database is not yet setup (when loading
|
|
|
|
# the initial database schema), cached settings are only being read
|
|
|
|
# when the settings table exists.
|
|
|
|
#
|
2014-03-16 02:08:15 +01:00
|
|
|
# @param key [String, Symbol]
|
|
|
|
# @return [Object] Value of the key.
|
2012-08-24 19:52:38 +02:00
|
|
|
def [](key)
|
2015-01-14 21:15:08 +01:00
|
|
|
if RailsSettings::CachedSettings.table_exists? && allowed_key?(key)
|
2023-05-12 13:01:12 +02:00
|
|
|
value = RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"]
|
2014-03-16 02:08:15 +01:00
|
|
|
value = config[key] if value.nil?
|
|
|
|
value
|
|
|
|
else
|
|
|
|
config[key]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Store configuration in the database.
|
|
|
|
#
|
|
|
|
# If value is equal to what's defined in the configuration file, remove key from the database.
|
|
|
|
# @param key [String, Symbol] Key
|
|
|
|
# @param value [Object] Value
|
|
|
|
# @return [Boolean] Whether storing succeeded (fails when key is not allowed to be set in database).
|
|
|
|
def []=(key, value)
|
|
|
|
return false unless allowed_key?(key)
|
2021-03-01 15:27:26 +01:00
|
|
|
|
2014-09-02 14:43:45 +02:00
|
|
|
value = normalize_value value
|
2014-03-16 02:08:15 +01:00
|
|
|
# then update database
|
2015-01-14 21:15:08 +01:00
|
|
|
if config[key] == value || (config[key].nil? && value == false)
|
2014-03-16 02:08:15 +01:00
|
|
|
# delete (ok if it was already deleted)
|
|
|
|
begin
|
2023-05-12 13:01:12 +02:00
|
|
|
RailsSettings::CachedSettings.destroy "foodcoop.#{scope}.#{key}"
|
2014-03-16 02:08:15 +01:00
|
|
|
rescue RailsSettings::Settings::SettingNotFound
|
|
|
|
end
|
|
|
|
else
|
|
|
|
# or store
|
2023-05-12 13:01:12 +02:00
|
|
|
RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"] = value
|
2014-03-16 02:08:15 +01:00
|
|
|
end
|
2021-03-01 15:27:26 +01:00
|
|
|
true
|
2014-03-16 02:08:15 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# @return [Array<String>] Configuration keys that are set (either in +app_config.yml+ or database).
|
|
|
|
def keys
|
2023-05-12 13:01:12 +02:00
|
|
|
keys = RailsSettings::CachedSettings.get_all("foodcoop.#{scope}.").try(:keys) || []
|
|
|
|
keys.map! { |k| k.gsub(/^foodcoop\.#{scope}\./, '') }
|
2014-03-16 02:08:15 +01:00
|
|
|
keys += config.keys
|
|
|
|
keys.map(&:to_s).uniq
|
2012-08-24 19:52:38 +02:00
|
|
|
end
|
|
|
|
|
2016-02-26 14:42:16 +01:00
|
|
|
# @return [Array<String>] Valid names of foodcoops.
|
|
|
|
def foodcoops
|
2014-05-30 12:13:09 +02:00
|
|
|
if config[:multi_coop_install]
|
2022-02-20 16:15:22 +01:00
|
|
|
APP_CONFIG.keys.grep_v(/^(default|development|test|production)$/)
|
2014-05-30 12:13:09 +02:00
|
|
|
else
|
2017-02-14 12:00:19 +01:00
|
|
|
[config[:default_scope]]
|
2016-02-26 14:42:16 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Loop through each foodcoop and executes the given block after setup config and database
|
|
|
|
def each_coop
|
|
|
|
foodcoops.each do |coop|
|
|
|
|
select_multifoodcoop coop
|
|
|
|
yield coop
|
2012-08-27 08:24:25 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-23 10:17:33 +02:00
|
|
|
def allowed_foodcoop?(foodcoop)
|
|
|
|
foodcoops.include? foodcoop
|
|
|
|
end
|
|
|
|
|
2014-03-16 02:08:15 +01:00
|
|
|
# @return [Boolean] Whether this key may be set in the database
|
|
|
|
def allowed_key?(key)
|
|
|
|
# fast check for keys without nesting
|
2023-05-12 13:01:12 +02:00
|
|
|
if config[:protected].include? key
|
|
|
|
!config[:protected][key]
|
2014-06-27 09:07:47 +02:00
|
|
|
else
|
2023-05-12 13:01:12 +02:00
|
|
|
!config[:protected][:all]
|
2014-06-27 09:07:47 +02:00
|
|
|
end
|
2014-03-16 02:08:15 +01:00
|
|
|
# @todo allow to check nested keys as well
|
|
|
|
end
|
|
|
|
|
2014-06-25 16:21:36 +02:00
|
|
|
# @return [Hash] Full configuration.
|
|
|
|
def to_hash
|
2023-01-06 16:27:41 +01:00
|
|
|
keys.index_with { |k| self[k] }
|
2014-06-25 16:21:36 +02:00
|
|
|
end
|
2014-06-23 10:10:21 +02:00
|
|
|
|
2018-10-13 15:18:55 +02:00
|
|
|
# for using active_model_serializer in the api/v1/configs controller
|
2021-03-01 15:27:26 +01:00
|
|
|
alias read_attribute_for_serialization []
|
2014-06-23 10:10:21 +02:00
|
|
|
|
|
|
|
# @!attribute default_config
|
|
|
|
# Returns the program-default foodcoop configuration.
|
|
|
|
#
|
|
|
|
# Plugins (engines in Rails terms) can easily add to the default
|
|
|
|
# configuration by defining a method +default_foodsoft_config+ in
|
|
|
|
# their engine and modify the {Hash} passed.
|
|
|
|
#
|
|
|
|
# When modifying this, please make sure to use default values that
|
|
|
|
# match old behaviour. For example, when the wiki was made optional
|
|
|
|
# and turned into a plugin, the configuration item +use_wiki+ was
|
|
|
|
# introduced with a default value of +true+ (set in the wiki plugin):
|
|
|
|
#
|
|
|
|
# module FoodsoftWiki
|
|
|
|
# class Engine < ::Rails::Engine
|
|
|
|
# def default_foodsoft_config(cfg)
|
|
|
|
# cfg[:use_wiki] = true # keep backward compatibility
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# @return [Hash] Default configuration values
|
|
|
|
|
2012-08-24 19:52:38 +02:00
|
|
|
private
|
|
|
|
|
|
|
|
def set_config(foodcoop)
|
|
|
|
raise "No config for this environment (#{foodcoop}) available!" if APP_CONFIG[foodcoop].nil?
|
2021-03-01 15:27:26 +01:00
|
|
|
|
2014-03-16 02:08:15 +01:00
|
|
|
self.config = APP_CONFIG[foodcoop]
|
2012-08-24 19:52:38 +02:00
|
|
|
self.scope = foodcoop
|
2014-04-25 17:34:35 +02:00
|
|
|
set_missing
|
2012-08-24 19:52:38 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def setup_database
|
2023-01-17 14:37:10 +01:00
|
|
|
database_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
|
2014-04-05 10:41:45 +02:00
|
|
|
database_config = database_config.merge(config[:database]) if config[:database].present?
|
2012-08-24 19:52:38 +02:00
|
|
|
ActiveRecord::Base.establish_connection(database_config)
|
|
|
|
end
|
|
|
|
|
2017-08-17 23:49:34 +02:00
|
|
|
def setup_mailing
|
|
|
|
ActionMailer::Base.default_url_options[:foodcoop] = scope
|
|
|
|
end
|
|
|
|
|
2014-06-25 16:21:36 +02:00
|
|
|
# Completes foodcoop configuration with program defaults.
|
|
|
|
# @see #foodsoft_config
|
2013-09-20 22:40:13 +02:00
|
|
|
def set_missing
|
2014-06-25 16:21:36 +02:00
|
|
|
config.replace(default_config.deep_merge(config))
|
2014-06-23 10:10:21 +02:00
|
|
|
end
|
|
|
|
|
2014-06-25 16:21:36 +02:00
|
|
|
# Returns program-default configuration.
|
|
|
|
# When new options are introduced, put backward-compatible defaults here, so that
|
|
|
|
# configuration files that haven't been updated, still work as they did. This also
|
|
|
|
# makes sure that the configuration editor picks up the defaults.
|
2014-06-23 10:10:21 +02:00
|
|
|
# @return [Hash] Program-default foodcoop configuration.
|
|
|
|
# @see #default_config
|
2014-06-25 16:21:36 +02:00
|
|
|
# @see #set_missing
|
2014-06-23 10:10:21 +02:00
|
|
|
def get_default_config
|
|
|
|
cfg = {
|
2014-02-25 10:53:28 +01:00
|
|
|
use_nick: true,
|
2014-06-06 17:50:20 +02:00
|
|
|
use_apple_points: true,
|
2014-03-16 02:08:15 +01:00
|
|
|
# English is the default language, and this makes it show up as default.
|
|
|
|
default_locale: 'en',
|
2022-01-21 15:12:22 +01:00
|
|
|
time_zone: 'Berlin',
|
2014-06-27 11:45:34 +02:00
|
|
|
currency_unit: '€',
|
|
|
|
currency_space: true,
|
2014-03-16 02:08:15 +01:00
|
|
|
foodsoft_url: 'https://github.com/foodcoops/foodsoft',
|
2014-09-30 15:09:47 +02:00
|
|
|
contact: {}, # avoid errors when undefined
|
2014-11-23 01:22:50 +01:00
|
|
|
tasks_period_days: 7,
|
|
|
|
tasks_upfront_days: 49,
|
2019-01-15 02:55:12 +01:00
|
|
|
shared_supplier_article_sync_limit: 200,
|
2021-02-05 16:19:05 +01:00
|
|
|
distribution_strategy: FoodsoftConfig::DistributionStrategy::FIRST_ORDER_FIRST_SERVE,
|
2014-06-25 16:21:36 +02:00
|
|
|
# The following keys cannot, by default, be set by foodcoops themselves.
|
2014-03-16 02:08:15 +01:00
|
|
|
protected: {
|
2014-06-25 16:21:36 +02:00
|
|
|
multi_coop_install: true,
|
|
|
|
default_scope: true,
|
|
|
|
notification: true,
|
|
|
|
shared_lists: true,
|
|
|
|
protected: true,
|
|
|
|
database: true
|
2014-03-16 02:08:15 +01:00
|
|
|
}
|
2014-06-23 10:10:21 +02:00
|
|
|
}
|
|
|
|
# allow engines to easily add to this
|
2014-11-21 14:37:56 +01:00
|
|
|
engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:default_foodsoft_config) }
|
2014-06-23 10:10:21 +02:00
|
|
|
engines.each { |e| e.default_foodsoft_config(cfg) }
|
|
|
|
cfg
|
2014-04-04 12:25:57 +02:00
|
|
|
end
|
|
|
|
|
2014-09-02 14:43:45 +02:00
|
|
|
# Normalize value recursively (which can be entered as strings, but we want to store it properly)
|
|
|
|
def normalize_value(value)
|
2021-03-01 15:27:26 +01:00
|
|
|
value = value.map { |v| normalize_value(v) } if value.is_a? Array
|
2014-09-02 16:19:08 +02:00
|
|
|
if value.is_a? Hash
|
2023-05-12 13:01:12 +02:00
|
|
|
value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map do |a|
|
|
|
|
[a[0], normalize_value(a[1])]
|
|
|
|
end]
|
2014-09-02 16:19:08 +02:00
|
|
|
end
|
2014-09-02 14:43:45 +02:00
|
|
|
case value
|
2021-03-01 15:27:26 +01:00
|
|
|
when 'true' then true
|
|
|
|
when 'false' then false
|
|
|
|
when /^[-+0-9]+$/ then value.to_i
|
|
|
|
when /^[-+0-9.]+([eE][-+0-9]+)?$/ then value.to_f
|
|
|
|
when '' then nil
|
|
|
|
else value
|
2014-09-02 14:43:45 +02:00
|
|
|
end
|
|
|
|
end
|
2012-08-24 19:52:38 +02:00
|
|
|
end
|
2013-09-20 22:40:13 +02:00
|
|
|
end
|