package config import ( "fmt" "os" "strconv" "strings" "time" "github.com/golang-jwt/jwt/v5" "github.com/spf13/viper" ) type Config struct { Server ServerConfig Database DatabaseConfig Auth AuthConfig AI AIConfig JWT JWTConfig } type ServerConfig struct { Host string Port string ReadTimeout time.Duration WriteTimeout time.Duration } type DatabaseConfig struct { Host string Port string User string Password string DBName string SSLMode string TimeZone string } type AuthConfig struct { JWTSecret string JWTExpirationHours int BCryptCost int MaxLoginAttempts int AccountLockDuration time.Duration } type AIConfig struct { OpenAI APIConfig Local APIConfig } type APIConfig struct { APIKey string Endpoint string Model string MaxTokens int Temperature float64 TopP float64 Timeout time.Duration } type JWTConfig struct { Secret string ExpirationHours int Issuer string Audience string } var AppConfig Config // LoadConfig loads configuration from environment variables and config file func LoadConfig() error { // Set default values viper.SetDefault("server.host", "0.0.0.0") viper.SetDefault("server.port", "8080") viper.SetDefault("server.readTimeout", "15s") viper.SetDefault("server.writeTimeout", "15s") viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", "5432") viper.SetDefault("database.user", "postgres") viper.SetDefault("database.password", "postgres") viper.SetDefault("database.dbname", "support") viper.SetDefault("database.sslmode", "disable") viper.SetDefault("database.timezone", "UTC") viper.SetDefault("auth.jwtSecret", "your-secret-key") viper.SetDefault("auth.jwtExpirationHours", 24) viper.SetDefault("auth.bcryptCost", 12) viper.SetDefault("auth.maxLoginAttempts", 5) viper.SetDefault("auth.accountLockDuration", "30m") viper.SetDefault("ai.openai.maxTokens", 4000) viper.SetDefault("ai.openai.temperature", 0.7) viper.SetDefault("ai.openai.topP", 1.0) viper.SetDefault("ai.openai.timeout", "30s") viper.SetDefault("ai.local.maxTokens", 2000) viper.SetDefault("ai.local.temperature", 0.7) viper.SetDefault("ai.local.topP", 1.0) viper.SetDefault("ai.local.timeout", "60s") viper.SetDefault("ai.local.endpoint", "http://localhost:11434") viper.SetDefault("ai.local.model", "llama2") viper.SetDefault("jwt.secret", "your-jwt-secret") viper.SetDefault("jwt.expirationHours", 24) viper.SetDefault("jwt.issuer", "support-system") viper.SetDefault("jwt.audience", "support-users") // Configure viper to read from environment variables viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Read config file if it exists viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("./") viper.AddConfigPath("./config") viper.AddConfigPath("../config") if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return fmt.Errorf("error reading config file: %w", err) } // Config file not found; ignore error } // Unmarshal config if err := viper.Unmarshal(&AppConfig); err != nil { return fmt.Errorf("unable to decode config: %w", err) } return nil } // GetDSN returns the database connection string func (c *DatabaseConfig) GetDSN() string { return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s", c.Host, c.User, c.Password, c.DBName, c.Port, c.SSLMode, c.TimeZone) } // GetServerAddress returns the server address func (c *ServerConfig) GetServerAddress() string { return fmt.Sprintf("%s:%s", c.Host, c.Port) } // GetJWTSigningKey returns the JWT signing key func (c *JWTConfig) GetJWTSigningKey() []byte { return []byte(c.Secret) } // ParseJWTToken parses a JWT token and returns the claims func (c *JWTConfig) ParseJWTToken(tokenString string) (*jwt.MapClaims, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return c.GetJWTSigningKey(), nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return &claims, nil } return nil, fmt.Errorf("invalid token") } // GetEnv gets an environment variable with a default value func GetEnv(key, defaultValue string) string { if value, exists := os.LookupEnv(key); exists { return value } return defaultValue } // GetEnvAsInt gets an environment variable as an integer with a default value func GetEnvAsInt(key string, defaultValue int) int { valueStr := GetEnv(key, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return defaultValue } // GetEnvAsBool gets an environment variable as a boolean with a default value func GetEnvAsBool(key string, defaultValue bool) bool { valueStr := GetEnv(key, "") if value, err := strconv.ParseBool(valueStr); err == nil { return value } return defaultValue } // GetEnvAsDuration gets an environment variable as a duration with a default value func GetEnvAsDuration(key string, defaultValue time.Duration) time.Duration { valueStr := GetEnv(key, "") if value, err := time.ParseDuration(valueStr); err == nil { return value } return defaultValue }