Перейти к содержанию

UnifiedConfig System Guide

Architecture Overview

UnifiedConfig принципы:

  • Single Source of Truth - все настройки в одном месте
  • Modular Structure - 7 специализированных config/ модулей
  • Auto-Generation - config.yaml генерируется автоматически
  • Environment Variable Support - ${ENV_VAR} подстановка
  • Type Safety - строгая валидация при загрузке

Modular Config Structure

7 Обязательных Модулей:

config/
├── network.yaml      # Сервер, CORS, домены, сетевые настройки
├── database.yaml     # PostgreSQL, пулы соединений
├── auth.yaml         # JWT, Web3 auth, администраторы
├── blockchain.yaml   # Сети, контракты, стейкинг
├── testing.yaml      # Тестовые настройки
├── monitoring.yaml   # Логирование, мониторинг, метрики
└── limits.yaml       # Лимиты, стратегии, faucet

Generated Files:

  • config.yaml (read-only, chmod 444) - единая конфигурация
  • frontend/shared/config/generated-config.ts (read-only) - TypeScript config
  • .env.generated (read-only) - публичные env variables

Configuration Loading

Go Backend:

import "github.com/aiseeq/saga/backend/config"

func main() {
    // Load unified configuration
    cfg, err := config.LoadUnifiedConfig()
    if err != nil {
        log.Fatalf("Failed to load config: %v", err)
    }

    // Access configuration
    serverPort := cfg.GetServerPort()
    jwtSecret := cfg.GetJWTSecret()
    dbURL := cfg.GetDatabaseURL()

    // Use in services
    jwtService := jwt_service.NewJWTService(cfg)
    balanceService := balance_service.NewBalanceService(db, cfg)
}

Configuration Interface:

type UnifiedConfig interface {
    // Server Configuration
    GetServerHost() string
    GetServerPort() int
    GetAllowedOrigins() []string

    // Database Configuration
    GetDatabaseURL() string
    GetDatabaseMaxConnections() int
    GetDatabaseMaxIdleConnections() int

    // JWT Configuration
    GetJWTSecret() string
    GetJWTExpirationHours() int
    GetJWTIssuer() string

    // Blockchain Configuration
    GetBlockchainRPCURL() string
    GetNetworkID() int
    GetUSDCContractAddress() string

    // Admin Management
    IsAdmin(email string) bool
    GetAdminWallets() []string

    // Investment Strategies
    GetInvestmentStrategy(id int) *InvestmentStrategy
    GetAllInvestmentStrategies() []*InvestmentStrategy

    // Faucet Configuration
    GetFaucetAmountUSDC() SafeDecimal
    GetFaucetCooldownHours() int
}

🛠️ Working with Config Modules

Adding New Configuration:

1. Choose Correct Module:

  • Network settings → config/network.yaml
  • Auth/JWT → config/auth.yaml
  • Blockchain → config/blockchain.yaml
  • Business limits → config/limits.yaml

2. Edit Module File:

# config/limits.yaml
faucet:
  amount_usdc: "1000.00"
  cooldown_hours: 24
  new_setting: "value"  # Add here

3. Regenerate Config:

make generate-config

4. Add Getter Method:

// backend/config/unified_config_accessors.go
func (c *UnifiedConfig) GetFaucetNewSetting() string {
    return c.Faucet.NewSetting
}

Example: Adding Email Configuration

Step 1 - Add to config/network.yaml:

email:
  smtp_host: "${SMTP_HOST}"
  smtp_port: 587
  from_address: "noreply@saga.surf"

Step 2 - Regenerate:

make generate-config

Step 3 - Add Interface Method:

// In UnifiedConfig interface
GetEmailSMTPHost() string
GetEmailSMTPPort() int
GetEmailFromAddress() string

// In implementation
func (c *UnifiedConfig) GetEmailSMTPHost() string {
    return c.Email.SMTPHost
}

Step 4 - Use in Code:

func NewEmailService(cfg *config.UnifiedConfig) *EmailService {
    return &EmailService{
        smtpHost: cfg.GetEmailSMTPHost(),
        smtpPort: cfg.GetEmailSMTPPort(),
        from:     cfg.GetEmailFromAddress(),
    }
}

Environment Variables

Secure Secrets:

Principle: Secrets НЕ в config files, ТОЛЬКО в environment variables.

.env File (not committed):

# Database
DB_PASSWORD=secure-password

# JWT
JWT_SECRET=super-secret-minimum-32-characters-required

# Blockchain (optional for testnet)
HD_WALLET_MASTER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

# Email (if using)
SMTP_HOST=smtp.gmail.com
SMTP_PASSWORD=app-specific-password

Usage in Config:

# config/database.yaml
database:
  host: "127.0.0.1"
  port: 5432
  user: "aisee"
  password: "${DB_PASSWORD}"  # From .env
  database: "saga"

# config/auth.yaml
jwt:
  secret: "${JWT_SECRET}"  # From .env
  expiration_hours: 24

.env Loading:

Automatic in UnifiedConfig:

// backend/config/unified_config_loader.go
func LoadUnifiedConfig() (*UnifiedConfig, error) {
    // Auto-load .env file (using cleanenv library)
    if err := godotenv.Load(); err != nil {
        log.Warn("No .env file found, using environment variables only")
    }

    // Load config with env substitution
    var cfg UnifiedConfig
    if err := cleanenv.ReadConfig("config.yaml", &cfg); err != nil {
        return nil, fmt.Errorf("failed to load config: %w", err)
    }

    // Validate
    if err := cfg.Validate(); err != nil {
        return nil, fmt.Errorf("config validation failed: %w", err)
    }

    return &cfg, nil
}

Testing with UnifiedConfig

Test Configuration:

Example Test Configuration:

func LoadTestConfig() *config.UnifiedConfig {
    // Load from test-specific config
    cfg, err := config.LoadConfig("../../config.yaml")
    if err != nil {
        log.Fatalf("Failed to load test config: %v", err)
    }

    // Override for testing
    cfg.Server.Port = 8081  // Different port for tests
    cfg.Database.Name = "saga_test"  // Test database

    return cfg
}

Mock Config for Unit Tests:

type MockConfig struct {
    ServerPort      int
    JWTSecret       string
    DatabaseURL     string
    AdminEmails     []string
}

func (m *MockConfig) GetServerPort() int { return m.ServerPort }
func (m *MockConfig) GetJWTSecret() string { return m.JWTSecret }
// ... implement other interface methods

func TestServiceWithMockConfig(t *testing.T) {
    mockCfg := &MockConfig{
        JWTSecret: "test-secret",
        AdminEmails: []string{"admin@test.com"},
    }

    service := NewService(mockCfg)
    // ... test service
}

Configuration Validation

Automatic Validation:

// backend/config/unified_config_validator.go
func (c *UnifiedConfig) Validate() error {
    var errs []string

    // Validate server
    if c.Server.Port < 1024 || c.Server.Port > 65535 {
        errs = append(errs, "invalid server port")
    }

    // Validate JWT
    if len(c.JWT.Secret) < 32 {
        errs = append(errs, "JWT secret must be at least 32 characters")
    }

    // Validate database
    if c.Database.MaxConnections < 1 {
        errs = append(errs, "database max connections must be >= 1")
    }

    // Validate blockchain
    if !strings.HasPrefix(c.Blockchain.USDC.ContractAddress, "0x") {
        errs = append(errs, "invalid USDC contract address")
    }

    if len(errs) > 0 {
        return fmt.Errorf("config validation failed: %s", strings.Join(errs, "; "))
    }

    return nil
}

Custom Validators:

func ValidateInvestmentStrategies(strategies []*InvestmentStrategy) error {
    for _, s := range strategies {
        if s.APY <= 0 || s.APY > 100 {
            return fmt.Errorf("invalid APY for strategy %d: %f", s.ID, s.APY)
        }

        if !common.IsHexAddress(s.ContractAddress) {
            return fmt.Errorf("invalid contract address for strategy %d", s.ID)
        }
    }

    return nil
}

Config Generation System

Master Config Generator:

Location: tools/master-config-generator/

Generate All Configs:

make generate-config

What Happens:

  1. Reads all config/ modules (network.yaml, database.yaml, etc.)
  2. Validates module structure
  3. Generates unified config.yaml
  4. Generates frontend config files
  5. Applies read-only protection (chmod 444)
  6. Updates .env.generated with public constants

Hardcode Analyzer:

Prevent Hardcoded Constants:

make analyze-hardcode-violations

Example Violation:

// ❌ WRONG (hardcode detected)
const ServerPort = 8080

// ✅ CORRECT (use UnifiedConfig)
port := cfg.GetServerPort()

Production Deployment

Environment-Specific Configs:

Development (.env.dev):

DB_PASSWORD=dev-password
JWT_SECRET=dev-secret-32-characters-minimum

Staging (.env.staging):

DB_PASSWORD=staging-secure-password
JWT_SECRET=staging-secret-64-characters-even-better

Production (.env.production):

DB_PASSWORD=${VAULT_DB_PASSWORD}
JWT_SECRET=${VAULT_JWT_SECRET}

Deployment Checklist:

  • Generate config: make generate-config
  • Set production .env variables
  • Validate config: make validate-config
  • Test config loading in staging
  • Deploy to production
  • Verify config is loaded correctly



📋 Метаданные

Версия: 2.4.82

Обновлено: 2025-10-21

Статус: Published