package config import ( "fmt" "os" "strings" "time" "github.com/spf13/viper" ) type Config struct { Server ServerConfig `mapstructure:"server"` Database DatabaseConfig `mapstructure:"database"` Redis RedisConfig `mapstructure:"redis"` Cache CacheConfig `mapstructure:"cache"` JWT JWTConfig `mapstructure:"jwt"` Log LogConfig `mapstructure:"log"` LLM LLMConfig `mapstructure:"llm"` Generation GenerationConfig `mapstructure:"generation"` } type ServerConfig struct { Port int `mapstructure:"port"` Mode string `mapstructure:"mode"` } type DatabaseConfig struct { Host string `mapstructure:"host"` Port int `mapstructure:"port"` User string `mapstructure:"user"` Password string `mapstructure:"password"` DBName string `mapstructure:"dbname"` SSLMode string `mapstructure:"sslmode"` MaxOpenConns int `mapstructure:"max_open_conns"` MaxIdleConns int `mapstructure:"max_idle_conns"` } func (d DatabaseConfig) DSN() string { return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", d.User, d.Password, d.Host, d.Port, d.DBName, d.SSLMode) } type RedisConfig struct { Addr string `mapstructure:"addr"` DB int `mapstructure:"db"` } type JWTConfig struct { Secret string `mapstructure:"secret"` AccessTTL time.Duration `mapstructure:"access_ttl"` RefreshTTL time.Duration `mapstructure:"refresh_ttl"` } type LogConfig struct { Level string `mapstructure:"level"` Format string `mapstructure:"format"` } type LLMConfig struct { Provider string `mapstructure:"provider"` APIKey string `mapstructure:"api_key"` BaseURL string `mapstructure:"base_url"` Model string `mapstructure:"model"` Timeout time.Duration `mapstructure:"timeout"` MaxOutputTokens int64 `mapstructure:"max_output_tokens"` Temperature float64 `mapstructure:"temperature"` TopP float64 `mapstructure:"top_p"` ReasoningEffort string `mapstructure:"reasoning_effort"` WebSearchLimit int32 `mapstructure:"web_search_limit"` } type CacheConfig struct { Driver string `mapstructure:"driver"` // "redis" or "memory" } type GenerationConfig struct { QueueSize int `mapstructure:"queue_size"` WorkerConcurrency int `mapstructure:"worker_concurrency"` StreamEnabled bool `mapstructure:"stream_enabled"` ArticleTimeout time.Duration `mapstructure:"article_timeout"` } func Load(configPath string) (*Config, error) { v := viper.New() v.SetConfigFile(configPath) v.AutomaticEnv() v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) if err := v.ReadInConfig(); err != nil { return nil, fmt.Errorf("read config: %w", err) } // Allow local overrides v.SetConfigFile(strings.Replace(configPath, ".yaml", ".local.yaml", 1)) _ = v.MergeInConfig() var cfg Config if err := v.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("unmarshal config: %w", err) } applyEnvOverrides(&cfg) return &cfg, nil } func applyEnvOverrides(cfg *Config) { if cfg == nil { return } if apiKey, ok := lookupNonEmptyEnv("LLM_API_KEY"); ok { cfg.LLM.APIKey = apiKey } } func lookupNonEmptyEnv(key string) (string, bool) { value, ok := os.LookupEnv(key) if !ok { return "", false } value = strings.TrimSpace(value) if value == "" { return "", false } return value, true }