Files
omega-server/local/model/system_config.go
2025-07-06 19:19:36 +02:00

281 lines
8.8 KiB
Go

package model
import (
"errors"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
// SystemConfig represents a system configuration setting
type SystemConfig struct {
BaseModel
Key string `json:"key" gorm:"unique;not null;type:varchar(255)"`
Value string `json:"value" gorm:"type:text"`
DefaultValue string `json:"defaultValue" gorm:"type:text"`
Description string `json:"description" gorm:"type:text"`
Category string `json:"category" gorm:"type:varchar(100)"`
DataType string `json:"dataType" gorm:"type:varchar(50)"` // string, integer, boolean, json
IsEditable bool `json:"isEditable" gorm:"default:true"`
IsSecret bool `json:"isSecret" gorm:"default:false"` // For sensitive values
DateModified string `json:"dateModified" gorm:"type:varchar(50)"`
}
// SystemConfigCreateRequest represents the request to create a new system config
type SystemConfigCreateRequest struct {
Key string `json:"key" validate:"required,min=3,max=255"`
Value string `json:"value"`
DefaultValue string `json:"defaultValue"`
Description string `json:"description" validate:"max=1000"`
Category string `json:"category" validate:"required,max=100"`
DataType string `json:"dataType" validate:"required,oneof=string integer boolean json"`
IsEditable bool `json:"isEditable"`
IsSecret bool `json:"isSecret"`
}
// SystemConfigUpdateRequest represents the request to update a system config
type SystemConfigUpdateRequest struct {
Value *string `json:"value,omitempty"`
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
Category *string `json:"category,omitempty" validate:"omitempty,max=100"`
DataType *string `json:"dataType,omitempty" validate:"omitempty,oneof=string integer boolean json"`
IsEditable *bool `json:"isEditable,omitempty"`
IsSecret *bool `json:"isSecret,omitempty"`
}
// SystemConfigInfo represents public system config information
type SystemConfigInfo struct {
ID string `json:"id"`
Key string `json:"key"`
Value string `json:"value,omitempty"` // Omitted if secret
DefaultValue string `json:"defaultValue,omitempty"`
Description string `json:"description"`
Category string `json:"category"`
DataType string `json:"dataType"`
IsEditable bool `json:"isEditable"`
IsSecret bool `json:"isSecret"`
CreatedAt string `json:"created_at"`
DateModified string `json:"dateModified"`
}
// BeforeCreate is called before creating a system config
func (sc *SystemConfig) BeforeCreate(tx *gorm.DB) error {
sc.BaseModel.BeforeCreate()
// Normalize key and category
sc.Key = strings.ToLower(strings.TrimSpace(sc.Key))
sc.Category = strings.ToLower(strings.TrimSpace(sc.Category))
sc.Description = strings.TrimSpace(sc.Description)
sc.DateModified = time.Now().UTC().Format(time.RFC3339)
return sc.Validate()
}
// BeforeUpdate is called before updating a system config
func (sc *SystemConfig) BeforeUpdate(tx *gorm.DB) error {
sc.BaseModel.BeforeUpdate()
// Update modification timestamp
sc.DateModified = time.Now().UTC().Format(time.RFC3339)
// Normalize fields if they're being updated
if sc.Key != "" {
sc.Key = strings.ToLower(strings.TrimSpace(sc.Key))
}
if sc.Category != "" {
sc.Category = strings.ToLower(strings.TrimSpace(sc.Category))
}
if sc.Description != "" {
sc.Description = strings.TrimSpace(sc.Description)
}
return sc.Validate()
}
// Validate validates system config data
func (sc *SystemConfig) Validate() error {
if sc.Key == "" {
return errors.New("configuration key is required")
}
if len(sc.Key) < 3 || len(sc.Key) > 255 {
return errors.New("configuration key must be between 3 and 255 characters")
}
if !isValidConfigKey(sc.Key) {
return errors.New("configuration key can only contain letters, numbers, dots, underscores, and hyphens")
}
if sc.Category == "" {
return errors.New("configuration category is required")
}
if len(sc.Category) > 100 {
return errors.New("configuration category must not exceed 100 characters")
}
if sc.DataType == "" {
return errors.New("configuration data type is required")
}
if !isValidDataType(sc.DataType) {
return errors.New("invalid data type, must be one of: string, integer, boolean, json")
}
if len(sc.Description) > 1000 {
return errors.New("configuration description must not exceed 1000 characters")
}
// Validate value according to data type
if sc.Value != "" {
if err := sc.ValidateValue(sc.Value); err != nil {
return err
}
}
return nil
}
// ValidateValue validates the configuration value according to its data type
func (sc *SystemConfig) ValidateValue(value string) error {
switch sc.DataType {
case "integer":
if _, err := strconv.Atoi(value); err != nil {
return errors.New("value must be a valid integer")
}
case "boolean":
if _, err := strconv.ParseBool(value); err != nil {
return errors.New("value must be a valid boolean (true/false)")
}
case "json":
// Basic JSON validation - check if it starts with { or [
trimmed := strings.TrimSpace(value)
if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") {
return errors.New("value must be valid JSON")
}
}
return nil
}
// ToSystemConfigInfo converts SystemConfig to SystemConfigInfo (public information)
func (sc *SystemConfig) ToSystemConfigInfo() SystemConfigInfo {
info := SystemConfigInfo{
ID: sc.ID,
Key: sc.Key,
DefaultValue: sc.DefaultValue,
Description: sc.Description,
Category: sc.Category,
DataType: sc.DataType,
IsEditable: sc.IsEditable,
IsSecret: sc.IsSecret,
CreatedAt: sc.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
DateModified: sc.DateModified,
}
// Only include value if it's not a secret
if !sc.IsSecret {
info.Value = sc.Value
}
return info
}
// GetStringValue returns the configuration value as a string
func (sc *SystemConfig) GetStringValue() string {
if sc.Value != "" {
return sc.Value
}
return sc.DefaultValue
}
// GetIntValue returns the configuration value as an integer
func (sc *SystemConfig) GetIntValue() (int, error) {
value := sc.GetStringValue()
return strconv.Atoi(value)
}
// GetBoolValue returns the configuration value as a boolean
func (sc *SystemConfig) GetBoolValue() (bool, error) {
value := sc.GetStringValue()
return strconv.ParseBool(value)
}
// GetFloatValue returns the configuration value as a float64
func (sc *SystemConfig) GetFloatValue() (float64, error) {
value := sc.GetStringValue()
return strconv.ParseFloat(value, 64)
}
// SetValue sets the configuration value with type validation
func (sc *SystemConfig) SetValue(value string) error {
if err := sc.ValidateValue(value); err != nil {
return err
}
sc.Value = value
sc.DateModified = time.Now().UTC().Format(time.RFC3339)
return nil
}
// ResetToDefault resets the configuration value to its default
func (sc *SystemConfig) ResetToDefault() {
sc.Value = sc.DefaultValue
sc.DateModified = time.Now().UTC().Format(time.RFC3339)
}
// isValidConfigKey validates configuration key format
func isValidConfigKey(key string) bool {
// Allow letters, numbers, dots, underscores, and hyphens
for _, char := range key {
if !((char >= 'a' && char <= 'z') ||
(char >= 'A' && char <= 'Z') ||
(char >= '0' && char <= '9') ||
char == '.' || char == '_' || char == '-') {
return false
}
}
return true
}
// isValidDataType validates the data type
func isValidDataType(dataType string) bool {
validTypes := []string{"string", "integer", "boolean", "json"}
for _, validType := range validTypes {
if dataType == validType {
return true
}
}
return false
}
// Common system configuration categories
const (
ConfigCategoryGeneral = "general"
ConfigCategorySecurity = "security"
ConfigCategoryEmail = "email"
ConfigCategoryAPI = "api"
ConfigCategoryLogging = "logging"
ConfigCategoryStorage = "storage"
ConfigCategoryCache = "cache"
)
// Common system configuration keys
const (
ConfigKeyAppName = "app.name"
ConfigKeyAppVersion = "app.version"
ConfigKeyAppDescription = "app.description"
ConfigKeyJWTExpiryHours = "security.jwt_expiry_hours"
ConfigKeyPasswordMinLength = "security.password_min_length"
ConfigKeyMaxLoginAttempts = "security.max_login_attempts"
ConfigKeyLockoutDurationMinutes = "security.lockout_duration_minutes"
ConfigKeySessionTimeoutMinutes = "security.session_timeout_minutes"
ConfigKeyRateLimitRequests = "security.rate_limit_requests"
ConfigKeyRateLimitWindow = "security.rate_limit_window_minutes"
ConfigKeyLogLevel = "logging.level"
ConfigKeyLogRetentionDays = "logging.retention_days"
ConfigKeyMaxFileUploadSize = "storage.max_file_upload_size_mb"
ConfigKeyCacheEnabled = "cache.enabled"
ConfigKeyCacheTTLMinutes = "cache.ttl_minutes"
)