package db
import (
"log/slog"
"strings"
"time"
"gopkg.awl.red/lx/internal/config"
"gopkg.awl.red/lx/internal/log"
)
func (d *DB) getConfig() error {
siteConfigSql := `SELECT
title,
support_email,
support_phone,
purpose,
disclaimer,
terms_and_conditions,
company,
company_url,
max_default_businesses,
listen_address,
port,
signup_needs_approve,
session_length_max,
email_confirm,
email_required,
email_regex,
password_salt_length,
password_key_length,
password_rounds,
password_hash_strategy,
password_attempts,
password_min,
password_regex,
password_help,
password_required,
log_add_source,
log_level,
log_level_key,
log_level_overrides,
log_time_format,
log_time_key,
analytics_disable,
analytics_write_timeout,
analytics_read_timeout,
person_style_text_color,
person_style_text_dark_color,
person_style_background_color,
person_style_background_dark_color,
merchant_default,
only_country,
favicon,
config_reload_seconds
FROM site_config;`
row := d.db.QueryRow(siteConfigSql)
cfg := &config.Config{}
err := row.Scan(
&cfg.Title, &cfg.SupportEmail, &cfg.SupportPhone, &cfg.Purpose,
&cfg.Disclaimer, &cfg.TermsAndConditions, &cfg.Company, &cfg.CompanyUrl,
&cfg.MaxDefaultBusinesses, &cfg.ListenAddress, &cfg.Port, &cfg.SignupNeedsApprove,
&cfg.SessionLengthMax,
&cfg.EmailConfirm, &cfg.EmailRequired, &cfg.EmailRegex,
&cfg.PasswordSaltLength, &cfg.PasswordKeyLength, &cfg.PasswordRounds, &cfg.PasswordHashStrategy,
&cfg.PasswordAttempts, &cfg.PasswordMin, &cfg.PasswordRegex, &cfg.PasswordHelp, &cfg.PasswordRequired,
&cfg.LogAddSource, &cfg.LogLevel, &cfg.LogLevelKey, &cfg.LogLevelOverrides, &cfg.LogTimeFormat, &cfg.LogTimeKey,
&cfg.AnalyticsDisable, &cfg.AnalyticsWriteTimeout, &cfg.AnalyticsReadTimeout,
&cfg.PersonStyleTextColor,
&cfg.PersonStyleTextDarkColor,
&cfg.PersonStyleBackgroundColor,
&cfg.PersonStyleBackgroundDarkColor,
&cfg.MerchantDefault, &cfg.OnlyCountry,
&cfg.Favicon,
&cfg.ConfigReloadSeconds,
)
if err != nil {
return err
}
businessTiersSql := `SELECT name, display_name, feature_level,
backhalves_anonymous, backhalves_named, folders,
analytics_retention, analytics_max_granularity, analytics_view_limit,
price_by_year, price_by_month
FROM business_tier;`
businessTiers := map[string]*config.BusinessTier{}
rows, err := d.db.Query(businessTiersSql)
if err != nil {
return err
}
for rows.Next() {
b := &config.BusinessTier{}
err = rows.Scan(
&b.Name, &b.DisplayName, &b.FeatureLevel,
&b.BackhalvesAnonymous, &b.BackhalvesNamed, &b.Folders,
&b.AnalyticsRetention, &b.AnalyticsMaxGranularity, &b.AnalyticsViewLimit,
&b.PriceByYear, &b.PriceByMonth,
)
if err != nil {
return err
}
businessTiers[b.Name] = b
}
err = rows.Err()
if err != nil {
return err
}
cfg.BusinessTiers = businessTiers
merchantSql := `SELECT m.name, m.enabled, m.api_url,
group_concat(mbt.merchant_business_tier_id_string),
group_concat(bt.name)
FROM merchant m
LEFT JOIN merchant_business_tier mbt ON m.id = mbt.merchant_id
LEFT JOIN business_tier bt ON bt.id = mbt.business_tier_id
GROUP BY m.id
;`
merchants := map[string]*config.Merchant{}
rows, err = d.db.Query(merchantSql)
if err != nil {
return err
}
for rows.Next() {
m := &config.Merchant{
TierPlanIds: map[string]string{},
PlanIdTiers: map[string]string{},
}
var planIdsStr string
var tiersStr string
err = rows.Scan(
&m.Name, &m.Enabled, &m.ApiUrl, &planIdsStr, &tiersStr,
)
slog.Info("MERCHANT", "name", m.Name, "enabled", m.Enabled, "plans", planIdsStr, "tiers", tiersStr)
if err != nil {
return err
}
tiers := strings.Split(tiersStr, ",")
planIds := strings.Split(planIdsStr, ",")
for i := range tiers {
m.TierPlanIds[tiers[i]] = planIds[i]
m.PlanIdTiers[planIds[i]] = tiers[i]
}
merchants[m.Name] = m
}
err = rows.Err()
if err != nil {
return err
}
cfg.Merchants = merchants
// siteUsageLimitsSql := `SELECT name, noun, verb, subject, extra, extra_type, windows FROM site_usage_limits;`
// usagePolicies := map[string]*config.UsagePolicy{}
// rows, err := d.db.Query(siteUsageLimitsSql)
// if err != nil {
// return err
// }
// for rows.Next() {
// up := &config.UsagePolicy{}
// err = rows.Scan(&up.Name, &up.Noun, &up.Verb, &up.Subject, &up.Extra, &up.ExtraType, &up.Windows)
// if err != nil {
// return err
// }
// usagePolicies[up.Name] = up
// }
// err = rows.Err()
// if err != nil {
// return err
// }
// cfg.UsagePolicies = usagePolicies
d.muCfg.Lock()
defer d.muCfg.Unlock()
d.cfg = cfg
return nil
}
func (d *DB) GetConfig() *config.Config {
d.muCfg.RLock()
if d.cfg != nil {
defer d.muCfg.RUnlock()
return d.cfg
}
d.muCfg.RUnlock()
err := d.getConfig()
if err != nil {
slog.Error("could not get config", "err", err)
}
d.muCfg.RLock()
defer d.muCfg.RUnlock()
return d.cfg
}
func (d *DB) reloadConfigLoop() {
cfgOld := d.GetConfig()
i := 0
for {
var cfg *config.Config
if i == 0 {
cfg = cfgOld
} else {
d.getConfig()
cfg = d.GetConfig()
if cfgOld != nil && cfg != nil {
doLogChange(cfgOld, cfg)
doListenChange(cfgOld, cfg, d.chAddrChange)
}
cfgOld = cfg
}
if cfg == nil || cfg.ConfigReloadSeconds <= 0 {
slog.Info("config reloader exited, config nil or config_reload_seconds not positive")
return
}
time.Sleep(time.Second * time.Duration(cfg.ConfigReloadSeconds))
i += 1
}
}
func doLogChange(a *config.Config, b *config.Config) {
logDelta := []any{}
if a.LogAddSource != b.LogAddSource {
logDelta = append(logDelta,
"add_source_old", a.LogAddSource,
"add_source_new", b.LogAddSource,
)
}
if a.LogLevel != b.LogLevel {
logDelta = append(logDelta,
"level_old", a.LogLevel,
"level_new", b.LogLevel,
)
}
if a.LogLevelKey != b.LogLevelKey {
logDelta = append(logDelta,
"level_key_old", a.LogLevelKey,
"level_key_new", b.LogLevelKey,
)
}
if a.LogLevelOverrides != b.LogLevelOverrides {
logDelta = append(logDelta,
"level_overrides_old", a.LogLevelOverrides,
"level_overrides_new", b.LogLevelOverrides,
)
}
if a.LogTimeFormat != b.LogTimeFormat {
logDelta = append(logDelta,
"time_format_old", a.LogTimeFormat,
"time_format_new", b.LogTimeFormat,
)
}
if a.LogTimeKey != b.LogTimeKey {
logDelta = append(logDelta,
"time_key_old", a.LogTimeKey,
"time_key_new", b.LogTimeKey,
)
}
if len(logDelta) > 0 {
log.SetFromConfig(b)
slog.Info("logger changed", logDelta...)
}
}
func doListenChange(a *config.Config, b *config.Config, chAddrChange chan config.AddrChange) {
logDelta := []any{}
if a.ListenAddress != b.ListenAddress {
logDelta = append(logDelta,
"listen_address_old", a.ListenAddress,
"listen_address_new", b.ListenAddress,
)
}
if a.Port != b.Port {
logDelta = append(logDelta,
"port_old", a.Port,
"port_new", b.Port,
)
}
if len(logDelta) > 0 {
// TODO: signal server to stop listening
chAddrChange <- config.AddrChange{Address: b.ListenAddress, Port: b.Port}
slog.Info("server listen changed", logDelta...)
}
}
type ConfigCacher interface {
GetConfig() *config.Config
}