281 lines
8.8 KiB
Go
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"
|
|
)
|