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 }