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"` DateCreated string `json:"dateCreated"` 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, DateCreated: sc.DateCreated.Format("2006-01-02T15:04:05Z"), 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" )