From 1df804a14a6ce9ff17f2ad3a3ff38424d55ff266 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Thu, 12 Mar 2026 19:06:20 +0800 Subject: [PATCH] Feature (System Settings): Implemented system settings management functionality (#13556) ### What problem does this PR solve? Feature (System Settings): Implemented system settings management functionality - Added a new SystemSettings model, including creation and update time fields. - Implemented SystemSettingsDAO, providing CRUD operations and transaction support. - Implemented management interfaces for variables, configurations, and environment variables in the admin service. ### Type of change - [x] New Feature (non-breaking change which adds functionality) Co-authored-by: Yingfeng --- internal/admin/handler.go | 74 ++++++++++--- internal/admin/service.go | 173 +++++++++++++++++++++++++---- internal/dao/system_settings.go | 188 ++++++++++++++++++++++++++++++++ internal/model/system.go | 14 ++- 4 files changed, 410 insertions(+), 39 deletions(-) create mode 100644 internal/dao/system_settings.go diff --git a/internal/admin/handler.go b/internal/admin/handler.go index 8acbb8cb19..2dc0eb8803 100644 --- a/internal/admin/handler.go +++ b/internal/admin/handler.go @@ -767,28 +767,46 @@ func (h *Handler) RestartService(c *gin.Context) { } // GetVariables handle get variables +// Python logic: if request body is empty, list all variables; otherwise get single variable by var_name from body func (h *Handler) GetVariables(c *gin.Context) { - varName := c.Query("var_name") - - if varName != "" { - // Get single variable - variable, err := h.service.GetVariable(varName) + // Check if request has body content + if c.Request.ContentLength == 0 || c.Request.ContentLength == -1 { + // List all variables + variables, err := h.service.GetAllVariables() if err != nil { - errorResponse(c, err.Error(), 400) + errorResponse(c, err.Error(), 500) return } - success(c, variable, "") + success(c, variables, "") return } - // List all variables - variables, err := h.service.GetAllVariables() + // Get single variable by var_name from request body + var req struct { + VarName string `json:"var_name"` + } + if err := c.ShouldBindJSON(&req); err != nil { + errorResponse(c, "Invalid request body", 400) + return + } + + if req.VarName == "" { + errorResponse(c, "Var name is required", 400) + return + } + + variable, err := h.service.GetVariable(req.VarName) if err != nil { + // Check if it's an AdminException + if adminErr, ok := err.(*AdminException); ok { + errorResponse(c, adminErr.Message, 400) + return + } errorResponse(c, err.Error(), 500) return } - success(c, variables, "") + success(c, variable, "") } // SetVariableHTTPRequest set variable request @@ -798,15 +816,31 @@ type SetVariableHTTPRequest struct { } // SetVariable handle set variable +// Python logic: update or create a system setting with the given name and value func (h *Handler) SetVariable(c *gin.Context) { var req SetVariableHTTPRequest if err := c.ShouldBindJSON(&req); err != nil { - errorResponse(c, "Var name and value are required", 400) + errorResponse(c, "Var name is required", 400) + return + } + + if req.VarName == "" { + errorResponse(c, "Var name is required", 400) + return + } + + if req.VarValue == "" { + errorResponse(c, "Var value is required", 400) return } if err := h.service.SetVariable(req.VarName, req.VarValue); err != nil { - errorResponse(c, err.Error(), 400) + // Check if it's an AdminException + if adminErr, ok := err.(*AdminException); ok { + errorResponse(c, adminErr.Message, 400) + return + } + errorResponse(c, err.Error(), 500) return } @@ -814,10 +848,16 @@ func (h *Handler) SetVariable(c *gin.Context) { } // GetConfigs handle get configs +// Python logic: return all service configurations func (h *Handler) GetConfigs(c *gin.Context) { configs, err := h.service.GetAllConfigs() if err != nil { - errorResponse(c, err.Error(), 400) + // Check if it's an AdminException + if adminErr, ok := err.(*AdminException); ok { + errorResponse(c, adminErr.Message, 400) + return + } + errorResponse(c, err.Error(), 500) return } @@ -825,10 +865,16 @@ func (h *Handler) GetConfigs(c *gin.Context) { } // GetEnvironments handle get environments +// Python logic: return important environment variables func (h *Handler) GetEnvironments(c *gin.Context) { environments, err := h.service.GetAllEnvironments() if err != nil { - errorResponse(c, err.Error(), 400) + // Check if it's an AdminException + if adminErr, ok := err.(*AdminException); ok { + errorResponse(c, adminErr.Message, 400) + return + } + errorResponse(c, err.Error(), 500) return } diff --git a/internal/admin/service.go b/internal/admin/service.go index dc1fb74061..8d7cc2f59d 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -46,15 +46,17 @@ var ( // Service admin service layer type Service struct { - userDAO *dao.UserDAO - licenseDAO *dao.LicenseDAO + userDAO *dao.UserDAO + licenseDAO *dao.LicenseDAO + systemSettingsDAO *dao.SystemSettingsDAO } // NewService create admin service func NewService() *Service { return &Service{ - userDAO: dao.NewUserDAO(), - licenseDAO: dao.NewLicenseDAO(), + userDAO: dao.NewUserDAO(), + licenseDAO: dao.NewLicenseDAO(), + systemSettingsDAO: dao.NewSystemSettingsDAO(), } } @@ -888,43 +890,172 @@ func (s *Service) RestartService(serviceID string) (map[string]interface{}, erro // Variable/Settings methods -// GetVariable get variable -func (s *Service) GetVariable(varName string) (map[string]interface{}, error) { - // TODO: Implement with settings manager - return map[string]interface{}{ - "var_name": varName, - "var_value": "", - }, nil +// AdminException admin exception error +type AdminException struct { + Message string + Code int +} + +// Error implement error interface +func (e *AdminException) Error() string { + return e.Message +} + +// NewAdminException create admin exception +func NewAdminException(message string) *AdminException { + return &AdminException{ + Message: message, + Code: 400, + } +} + +// GetVariable get variable by name +// Returns the system setting with the given name +// Returns AdminException if the setting is not found +func (s *Service) GetVariable(varName string) ([]map[string]interface{}, error) { + settings, err := s.systemSettingsDAO.GetByName(varName) + if err != nil { + return nil, err + } + + if len(settings) == 0 { + return nil, NewAdminException("Can't get setting: " + varName) + } + + result := make([]map[string]interface{}, 0, len(settings)) + for _, setting := range settings { + result = append(result, map[string]interface{}{ + "name": setting.Name, + "source": setting.Source, + "data_type": setting.DataType, + "value": setting.Value, + }) + } + return result, nil } // GetAllVariables get all variables +// Returns all system settings from database func (s *Service) GetAllVariables() ([]map[string]interface{}, error) { - // TODO: Implement with settings manager - return []map[string]interface{}{}, nil + settings, err := s.systemSettingsDAO.GetAll() + if err != nil { + return nil, err + } + + result := make([]map[string]interface{}, 0, len(settings)) + for _, setting := range settings { + result = append(result, map[string]interface{}{ + "name": setting.Name, + "source": setting.Source, + "data_type": setting.DataType, + "value": setting.Value, + }) + } + return result, nil } // SetVariable set variable +// Creates or updates a system setting +// If the setting exists, updates it; otherwise creates a new one func (s *Service) SetVariable(varName, varValue string) error { - // TODO: Implement with settings manager - _ = varName - _ = varValue - return nil + settings, err := s.systemSettingsDAO.GetByName(varName) + if err != nil { + return err + } + + if len(settings) == 1 { + setting := &settings[0] + setting.Value = varValue + return s.systemSettingsDAO.UpdateByName(varName, setting) + } else if len(settings) > 1 { + return NewAdminException("Can't update more than 1 setting: " + varName) + } + + // Create new setting if it doesn't exist + // Determine data_type based on name and value + dataType := "string" + if len(varName) >= 7 && varName[:7] == "sandbox" { + dataType = "json" + } else if len(varName) >= 9 && varName[len(varName)-9:] == ".enabled" { + dataType = "boolean" + } + + newSetting := &model.SystemSettings{ + Name: varName, + Value: varValue, + Source: "admin", + DataType: dataType, + } + return s.systemSettingsDAO.Create(newSetting) } // Config methods // GetAllConfigs get all configs +// Returns all service configurations from the config file func (s *Service) GetAllConfigs() ([]map[string]interface{}, error) { - // TODO: Implement with config manager - return []map[string]interface{}{}, nil + result := server.GetAllConfigs() + return result, nil } // Environment methods // GetAllEnvironments get all environments +// Returns important environment variables func (s *Service) GetAllEnvironments() ([]map[string]interface{}, error) { - // TODO: Implement with environment manager - return []map[string]interface{}{}, nil + result := make([]map[string]interface{}, 0) + + // DOC_ENGINE + docEngine := os.Getenv("DOC_ENGINE") + if docEngine == "" { + docEngine = "elasticsearch" + } + result = append(result, map[string]interface{}{ + "env": "DOC_ENGINE", + "value": docEngine, + }) + + // DEFAULT_SUPERUSER_EMAIL + defaultSuperuserEmail := os.Getenv("DEFAULT_SUPERUSER_EMAIL") + if defaultSuperuserEmail == "" { + defaultSuperuserEmail = "admin@ragflow.io" + } + result = append(result, map[string]interface{}{ + "env": "DEFAULT_SUPERUSER_EMAIL", + "value": defaultSuperuserEmail, + }) + + // DB_TYPE + dbType := os.Getenv("DB_TYPE") + if dbType == "" { + dbType = "mysql" + } + result = append(result, map[string]interface{}{ + "env": "DB_TYPE", + "value": dbType, + }) + + // DEVICE + device := os.Getenv("DEVICE") + if device == "" { + device = "cpu" + } + result = append(result, map[string]interface{}{ + "env": "DEVICE", + "value": device, + }) + + // STORAGE_IMPL + storageImpl := os.Getenv("STORAGE_IMPL") + if storageImpl == "" { + storageImpl = "MINIO" + } + result = append(result, map[string]interface{}{ + "env": "STORAGE_IMPL", + "value": storageImpl, + }) + + return result, nil } // Version methods diff --git a/internal/dao/system_settings.go b/internal/dao/system_settings.go new file mode 100644 index 0000000000..858d63ba50 --- /dev/null +++ b/internal/dao/system_settings.go @@ -0,0 +1,188 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package dao + +import ( + "errors" + "time" + + "ragflow/internal/model" + + "gorm.io/gorm" +) + +// SystemSettingsDAO system settings data access object +type SystemSettingsDAO struct{} + +// NewSystemSettingsDAO create system settings DAO instance +func NewSystemSettingsDAO() *SystemSettingsDAO { + return &SystemSettingsDAO{} +} + +// GetAll get all system settings +// Returns all system settings records from database +func (d *SystemSettingsDAO) GetAll() ([]model.SystemSettings, error) { + var settings []model.SystemSettings + err := DB.Find(&settings).Error + if err != nil { + return nil, err + } + return settings, nil +} + +// GetByName get system settings by name +// Returns settings records that match the given name +func (d *SystemSettingsDAO) GetByName(name string) ([]model.SystemSettings, error) { + var settings []model.SystemSettings + err := DB.Where("name = ?", name).Find(&settings).Error + if err != nil { + return nil, err + } + return settings, nil +} + +// UpdateByName update system settings by name +// Updates the setting with the given name using the provided data +func (d *SystemSettingsDAO) UpdateByName(name string, setting *model.SystemSettings) error { + now := time.Now().Unix() + nowDate := time.Now() + + return DB.Model(&model.SystemSettings{}). + Where("name = ?", name). + Updates(map[string]interface{}{ + "value": setting.Value, + "source": setting.Source, + "data_type": setting.DataType, + "update_time": now, + "update_date": nowDate, + }).Error +} + +// Create create a new system setting +// Inserts a new system setting record into database +func (d *SystemSettingsDAO) Create(setting *model.SystemSettings) error { + now := time.Now().Unix() + nowDate := time.Now() + + setting.CreateTime = &now + setting.CreateDate = &nowDate + setting.UpdateTime = &now + setting.UpdateDate = &nowDate + + return DB.Create(setting).Error +} + +// SaveOrCreate update existing setting or create new one +// If setting exists, updates it; otherwise creates a new record +func (d *SystemSettingsDAO) SaveOrCreate(name string, value string, source string, dataType string) error { + settings, err := d.GetByName(name) + if err != nil { + return err + } + + if len(settings) == 1 { + setting := &settings[0] + setting.Value = value + return d.UpdateByName(name, setting) + } else if len(settings) > 1 { + return errors.New("can't update more than 1 setting: " + name) + } + + newSetting := &model.SystemSettings{ + Name: name, + Value: value, + Source: source, + DataType: dataType, + } + return d.Create(newSetting) +} + +// Count get total count of system settings +func (d *SystemSettingsDAO) Count() (int64, error) { + var count int64 + err := DB.Model(&model.SystemSettings{}).Count(&count).Error + return count, err +} + +// DeleteByName delete system setting by name +func (d *SystemSettingsDAO) DeleteByName(name string) error { + return DB.Where("name = ?", name).Delete(&model.SystemSettings{}).Error +} + +// Exists check if setting exists by name +func (d *SystemSettingsDAO) Exists(name string) (bool, error) { + var count int64 + err := DB.Model(&model.SystemSettings{}).Where("name = ?", name).Count(&count).Error + if err != nil { + return false, err + } + return count > 0, nil +} + +// GetBySource get system settings by source +func (d *SystemSettingsDAO) GetBySource(source string) ([]model.SystemSettings, error) { + var settings []model.SystemSettings + err := DB.Where("source = ?", source).Find(&settings).Error + if err != nil { + return nil, err + } + return settings, nil +} + +// GetByDataType get system settings by data type +func (d *SystemSettingsDAO) GetByDataType(dataType string) ([]model.SystemSettings, error) { + var settings []model.SystemSettings + err := DB.Where("data_type = ?", dataType).Find(&settings).Error + if err != nil { + return nil, err + } + return settings, nil +} + +// Transaction execute operations in a transaction +func (d *SystemSettingsDAO) Transaction(fn func(tx *gorm.DB) error) error { + return DB.Transaction(fn) +} + +// CreateWithTx create setting within transaction +func (d *SystemSettingsDAO) CreateWithTx(tx *gorm.DB, setting *model.SystemSettings) error { + now := time.Now().Unix() + nowDate := time.Now() + + setting.CreateTime = &now + setting.CreateDate = &nowDate + setting.UpdateTime = &now + setting.UpdateDate = &nowDate + + return tx.Create(setting).Error +} + +// UpdateByNameWithTx update setting within transaction +func (d *SystemSettingsDAO) UpdateByNameWithTx(tx *gorm.DB, name string, setting *model.SystemSettings) error { + now := time.Now().Unix() + nowDate := time.Now() + + return tx.Model(&model.SystemSettings{}). + Where("name = ?", name). + Updates(map[string]interface{}{ + "value": setting.Value, + "source": setting.Source, + "data_type": setting.DataType, + "update_time": now, + "update_date": nowDate, + }).Error +} diff --git a/internal/model/system.go b/internal/model/system.go index be94f1653a..a67645a6b8 100644 --- a/internal/model/system.go +++ b/internal/model/system.go @@ -16,12 +16,18 @@ package model +import "time" + // SystemSettings system settings model type SystemSettings struct { - Name string `gorm:"column:name;primaryKey;size:128" json:"name"` - Source string `gorm:"column:source;size:32;not null" json:"source"` - DataType string `gorm:"column:data_type;size:32;not null" json:"data_type"` - Value string `gorm:"column:value;type:longtext;not null" json:"value"` + Name string `gorm:"column:name;primaryKey;size:128" json:"name"` + Source string `gorm:"column:source;size:32;not null" json:"source"` + DataType string `gorm:"column:data_type;size:32;not null" json:"data_type"` + Value string `gorm:"column:value;type:longtext;not null" json:"value"` + CreateTime *int64 `gorm:"column:create_time" json:"create_time"` + CreateDate *time.Time `gorm:"column:create_date" json:"create_date"` + UpdateTime *int64 `gorm:"column:update_time" json:"update_time"` + UpdateDate *time.Time `gorm:"column:update_date" json:"update_date"` } // TableName specify table name