package config import ( "fmt" "os" "strconv" "strings" "time" "github.com/joho/godotenv" ) // ConfigProvider defines the interface for configuration operations type ConfigProvider interface { // GetString retrieves a string configuration value GetString(key string) string // GetInt retrieves an integer configuration value GetInt(key string) int // GetBool retrieves a boolean configuration value GetBool(key string) bool // GetDuration retrieves a duration configuration value GetDuration(key string) time.Duration // GetStringSlice retrieves a string slice configuration value GetStringSlice(key string) []string // IsSet checks if a configuration key is set IsSet(key string) bool // Validate validates all required configuration values Validate() error // GetDatabaseDSN constructs and returns the database connection string GetDatabaseDSN() string // GetServerAddress returns the server address in host:port format GetServerAddress() string // GetMetricsAddress returns the metrics server address in host:port format GetMetricsAddress() string // GetJWTSecret returns the JWT signing secret GetJWTSecret() string // IsDevelopment returns true if the environment is development IsDevelopment() bool // IsProduction returns true if the environment is production IsProduction() bool } // Config implements the ConfigProvider interface type Config struct { values map[string]string } // NewConfig creates a new configuration provider func NewConfig() ConfigProvider { // Load .env file if it exists _ = godotenv.Load() c := &Config{ values: make(map[string]string), } // Load environment variables for _, env := range os.Environ() { pair := strings.SplitN(env, "=", 2) if len(pair) == 2 { c.values[pair[0]] = pair[1] } } // Set defaults c.setDefaults() return c } func (c *Config) setDefaults() { defaults := map[string]string{ "APP_NAME": "api-key-service", "APP_VERSION": "1.0.0", "SERVER_HOST": "0.0.0.0", "SERVER_PORT": "8080", "SERVER_READ_TIMEOUT": "30s", "SERVER_WRITE_TIMEOUT": "30s", "SERVER_IDLE_TIMEOUT": "120s", "DB_HOST": "localhost", "DB_PORT": "5432", "DB_NAME": "kms", "DB_USER": "postgres", "DB_PASSWORD": "postgres", "DB_SSLMODE": "disable", "DB_MAX_OPEN_CONNS": "25", "DB_MAX_IDLE_CONNS": "25", "DB_CONN_MAX_LIFETIME": "5m", "MIGRATION_PATH": "./migrations", "LOG_LEVEL": "info", "LOG_FORMAT": "json", "RATE_LIMIT_ENABLED": "true", "RATE_LIMIT_RPS": "100", "RATE_LIMIT_BURST": "200", "CACHE_ENABLED": "false", "CACHE_TTL": "1h", "JWT_ISSUER": "api-key-service", "JWT_SECRET": "bootstrap-jwt-secret-change-in-production", "AUTH_PROVIDER": "header", // header or sso "AUTH_HEADER_USER_EMAIL": "X-User-Email", "SSO_PROVIDER_URL": "", "SSO_CLIENT_ID": "", "SSO_CLIENT_SECRET": "", "INTERNAL_APP_ID": "internal.api-key-service", "INTERNAL_HMAC_KEY": "bootstrap-hmac-key-change-in-production", "METRICS_ENABLED": "false", "METRICS_PORT": "9090", } for key, value := range defaults { if _, exists := c.values[key]; !exists { c.values[key] = value } } } // GetString retrieves a string configuration value func (c *Config) GetString(key string) string { return c.values[key] } // GetInt retrieves an integer configuration value func (c *Config) GetInt(key string) int { if value, exists := c.values[key]; exists { if intVal, err := strconv.Atoi(value); err == nil { return intVal } } return 0 } // GetBool retrieves a boolean configuration value func (c *Config) GetBool(key string) bool { if value, exists := c.values[key]; exists { if boolVal, err := strconv.ParseBool(value); err == nil { return boolVal } } return false } // GetDuration retrieves a duration configuration value func (c *Config) GetDuration(key string) time.Duration { if value, exists := c.values[key]; exists { if duration, err := time.ParseDuration(value); err == nil { return duration } } return 0 } // GetStringSlice retrieves a string slice configuration value func (c *Config) GetStringSlice(key string) []string { if value, exists := c.values[key]; exists { if value == "" { return []string{} } return strings.Split(value, ",") } return []string{} } // IsSet checks if a configuration key is set func (c *Config) IsSet(key string) bool { _, exists := c.values[key] return exists } // Validate validates all required configuration values func (c *Config) Validate() error { required := []string{ "DB_HOST", "DB_PORT", "DB_NAME", "DB_USER", "DB_PASSWORD", "SERVER_HOST", "SERVER_PORT", "INTERNAL_APP_ID", "INTERNAL_HMAC_KEY", "JWT_SECRET", } var missing []string for _, key := range required { if !c.IsSet(key) || c.GetString(key) == "" { missing = append(missing, key) } } if len(missing) > 0 { return fmt.Errorf("missing required configuration keys: %s", strings.Join(missing, ", ")) } // Validate specific values if c.GetInt("DB_PORT") <= 0 || c.GetInt("DB_PORT") > 65535 { return fmt.Errorf("DB_PORT must be a valid port number") } if c.GetInt("SERVER_PORT") <= 0 || c.GetInt("SERVER_PORT") > 65535 { return fmt.Errorf("SERVER_PORT must be a valid port number") } if c.GetDuration("SERVER_READ_TIMEOUT") <= 0 { return fmt.Errorf("SERVER_READ_TIMEOUT must be a positive duration") } if c.GetDuration("SERVER_WRITE_TIMEOUT") <= 0 { return fmt.Errorf("SERVER_WRITE_TIMEOUT must be a positive duration") } if c.GetDuration("DB_CONN_MAX_LIFETIME") <= 0 { return fmt.Errorf("DB_CONN_MAX_LIFETIME must be a positive duration") } authProvider := c.GetString("AUTH_PROVIDER") if authProvider != "header" && authProvider != "sso" { return fmt.Errorf("AUTH_PROVIDER must be either 'header' or 'sso'") } if authProvider == "sso" { if c.GetString("SSO_PROVIDER_URL") == "" { return fmt.Errorf("SSO_PROVIDER_URL is required when AUTH_PROVIDER is 'sso'") } if c.GetString("SSO_CLIENT_ID") == "" { return fmt.Errorf("SSO_CLIENT_ID is required when AUTH_PROVIDER is 'sso'") } if c.GetString("SSO_CLIENT_SECRET") == "" { return fmt.Errorf("SSO_CLIENT_SECRET is required when AUTH_PROVIDER is 'sso'") } } return nil } // GetDatabaseDSN constructs and returns the database connection string func (c *Config) GetDatabaseDSN() string { return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", c.GetString("DB_HOST"), c.GetInt("DB_PORT"), c.GetString("DB_USER"), c.GetString("DB_PASSWORD"), c.GetString("DB_NAME"), c.GetString("DB_SSLMODE"), ) } // GetServerAddress returns the server address in host:port format func (c *Config) GetServerAddress() string { return fmt.Sprintf("%s:%d", c.GetString("SERVER_HOST"), c.GetInt("SERVER_PORT")) } // GetMetricsAddress returns the metrics server address in host:port format func (c *Config) GetMetricsAddress() string { return fmt.Sprintf("%s:%d", c.GetString("SERVER_HOST"), c.GetInt("METRICS_PORT")) } // GetJWTSecret returns the JWT signing secret func (c *Config) GetJWTSecret() string { return c.GetString("JWT_SECRET") } // IsDevelopment returns true if the environment is development func (c *Config) IsDevelopment() bool { env := c.GetString("APP_ENV") return env == "development" || env == "dev" || env == "" } // IsProduction returns true if the environment is production func (c *Config) IsProduction() bool { env := c.GetString("APP_ENV") return env == "production" || env == "prod" }