From bf710aec5625c9dcb43c83d920318a036a135bae Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Thu, 22 May 2025 19:07:34 +0200 Subject: [PATCH] fix: custom logo not correctly loaded if UI configuration is disabled --- backend/internal/model/app_config.go | 13 +++--- .../internal/service/app_config_service.go | 40 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/backend/internal/model/app_config.go b/backend/internal/model/app_config.go index 4e15009e..e01d9ff5 100644 --- a/backend/internal/model/app_config.go +++ b/backend/internal/model/app_config.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "reflect" + "slices" "strconv" "strings" "time" @@ -107,24 +108,26 @@ func (c *AppConfig) ToAppConfigVariableSlice(showAll bool) []AppConfigVariable { return res } -func (c *AppConfig) FieldByKey(key string) (string, error) { +func (c *AppConfig) FieldByKey(key string) (defaultValue string, isInternal bool, err error) { rv := reflect.ValueOf(c).Elem() rt := rv.Type() // Find the field in the struct whose "key" tag matches for i := range rt.NumField() { // Grab only the first part of the key, if there's a comma with additional properties - tagValue, _, _ := strings.Cut(rt.Field(i).Tag.Get("key"), ",") - if tagValue != key { + tagValue := strings.Split(rt.Field(i).Tag.Get("key"), ",") + keyFromTag := tagValue[0] + isInternal = slices.Contains(tagValue, "internal") + if keyFromTag != key { continue } valueField := rv.Field(i).FieldByName("Value") - return valueField.String(), nil + return valueField.String(), isInternal, nil } // If we are here, the config key was not found - return "", AppConfigKeyNotFoundError{field: key} + return "", false, AppConfigKeyNotFoundError{field: key} } func (c *AppConfig) UpdateField(key string, value string, noInternal bool) error { diff --git a/backend/internal/service/app_config_service.go b/backend/internal/service/app_config_service.go index 3aa7c6a4..74b0977a 100644 --- a/backend/internal/service/app_config_service.go +++ b/backend/internal/service/app_config_service.go @@ -8,6 +8,7 @@ import ( "mime/multipart" "os" "reflect" + "slices" "strings" "sync/atomic" "time" @@ -188,7 +189,7 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon // Skip values that are internal only and can't be updated if value == "" { // Ignore errors here as we know the key exists - defaultValue, _ := defaultCfg.FieldByKey(key) + defaultValue, _, _ := defaultCfg.FieldByKey(key) err = cfg.UpdateField(key, defaultValue, true) } else { err = cfg.UpdateField(key, value, true) @@ -229,10 +230,6 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon // UpdateAppConfigValues func (s *AppConfigService) UpdateAppConfigValues(ctx context.Context, keysAndValues ...string) error { - if common.EnvConfig.UiConfigDisabled { - return &common.UiConfigDisabledError{} - } - // Count of keysAndValues must be even if len(keysAndValues)%2 != 0 { return errors.New("invalid number of arguments received") @@ -267,10 +264,13 @@ func (s *AppConfigService) UpdateAppConfigValues(ctx context.Context, keysAndVal // Ensure that the field is valid // We do this by grabbing the default value var defaultValue string - defaultValue, err = defaultCfg.FieldByKey(key) + defaultValue, isInternal, err := defaultCfg.FieldByKey(key) if err != nil { return fmt.Errorf("invalid configuration key '%s': %w", key, err) } + if !isInternal && common.EnvConfig.UiConfigDisabled { + return &common.UiConfigDisabledError{} + } // Update the in-memory config value // If the new value is an empty string, then we set the in-memory value to the default one @@ -351,7 +351,7 @@ func (s *AppConfigService) LoadDbConfig(ctx context.Context) (err error) { // If the UI config is disabled, only load from the env if common.EnvConfig.UiConfigDisabled { - dest, err = s.loadDbConfigFromEnv() + dest, err = s.loadDbConfigFromEnv(ctx, s.db) } else { dest, err = s.loadDbConfigInternal(ctx, s.db) } @@ -365,7 +365,7 @@ func (s *AppConfigService) LoadDbConfig(ctx context.Context) (err error) { return nil } -func (s *AppConfigService) loadDbConfigFromEnv() (*model.AppConfig, error) { +func (s *AppConfigService) loadDbConfigFromEnv(ctx context.Context, tx *gorm.DB) (*model.AppConfig, error) { // First, start from the default configuration dest := s.getDefaultDbConfig() @@ -375,9 +375,25 @@ func (s *AppConfigService) loadDbConfigFromEnv() (*model.AppConfig, error) { for i := range rt.NumField() { field := rt.Field(i) - // Get the value of the key tag, taking only what's before the comma - // The env var name is the key converted to SCREAMING_SNAKE_CASE - key, _, _ := strings.Cut(field.Tag.Get("key"), ",") + // Get the key and internal tag values + tagValue := strings.Split(field.Tag.Get("key"), ",") + key := tagValue[0] + isInternal := slices.Contains(tagValue, "internal") + + // Internal fields are loaded from the database as they can't be set from the environment + if isInternal { + var value string + err := tx.WithContext(ctx). + Model(&model.AppConfigVariable{}). + Where("key = ?", key). + Select("value"). + First(&value).Error + if err == nil { + rv.Field(i).FieldByName("Value").SetString(value) + } + continue + } + envVarName := utils.CamelCaseToScreamingSnakeCase(key) // Set the value if it's set @@ -396,7 +412,7 @@ func (s *AppConfigService) loadDbConfigInternal(ctx context.Context, tx *gorm.DB // Load all configuration values from the database // This loads all values in a single shot - loaded := []model.AppConfigVariable{} + var loaded []model.AppConfigVariable queryCtx, queryCancel := context.WithTimeout(ctx, 10*time.Second) defer queryCancel() err := tx.