package config import ( "fmt" "log/slog" "os" "strconv" "strings" "go.uber.org/fx" "sneak.berlin/go/webhooker/internal/globals" "sneak.berlin/go/webhooker/internal/logger" pkgconfig "sneak.berlin/go/webhooker/pkg/config" // Populates the environment from a ./.env file automatically for // development configuration. Kept in one place only (here). _ "github.com/joho/godotenv/autoload" ) const ( // EnvironmentDev represents development environment EnvironmentDev = "dev" // EnvironmentProd represents production environment EnvironmentProd = "prod" ) // nolint:revive // ConfigParams is a standard fx naming convention type ConfigParams struct { fx.In Globals *globals.Globals Logger *logger.Logger } type Config struct { DBURL string DataDir string Debug bool MaintenanceMode bool DevelopmentMode bool Environment string MetricsPassword string MetricsUsername string Port int SentryDSN string params *ConfigParams log *slog.Logger } // IsDev returns true if running in development environment func (c *Config) IsDev() bool { return c.Environment == EnvironmentDev } // IsProd returns true if running in production environment func (c *Config) IsProd() bool { return c.Environment == EnvironmentProd } // envString returns the env var value if set, otherwise falls back to pkgconfig. func envString(envKey, configKey string) string { if v := os.Getenv(envKey); v != "" { return v } return pkgconfig.GetString(configKey) } // envSecretString returns the env var value if set, otherwise falls back to pkgconfig secrets. func envSecretString(envKey, configKey string) string { if v := os.Getenv(envKey); v != "" { return v } return pkgconfig.GetSecretString(configKey) } // envBool returns the env var value parsed as bool, otherwise falls back to pkgconfig. func envBool(envKey, configKey string) bool { if v := os.Getenv(envKey); v != "" { return strings.EqualFold(v, "true") || v == "1" } return pkgconfig.GetBool(configKey) } // envInt returns the env var value parsed as int, otherwise falls back to pkgconfig. func envInt(envKey, configKey string, defaultValue ...int) int { if v := os.Getenv(envKey); v != "" { if i, err := strconv.Atoi(v); err == nil { return i } } return pkgconfig.GetInt(configKey, defaultValue...) } // nolint:revive // lc parameter is required by fx even if unused func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { log := params.Logger.Get() // Determine environment from WEBHOOKER_ENVIRONMENT env var, default to dev environment := os.Getenv("WEBHOOKER_ENVIRONMENT") if environment == "" { environment = EnvironmentDev } // Validate environment if environment != EnvironmentDev && environment != EnvironmentProd { return nil, fmt.Errorf("WEBHOOKER_ENVIRONMENT must be either '%s' or '%s', got '%s'", EnvironmentDev, EnvironmentProd, environment) } // Set the environment in the config package (for fallback resolution) pkgconfig.SetEnvironment(environment) // Load configuration values — env vars take precedence over config.yaml s := &Config{ DBURL: envString("DBURL", "dburl"), DataDir: envString("DATA_DIR", "dataDir"), Debug: envBool("DEBUG", "debug"), MaintenanceMode: envBool("MAINTENANCE_MODE", "maintenanceMode"), DevelopmentMode: envBool("DEVELOPMENT_MODE", "developmentMode"), Environment: environment, MetricsUsername: envString("METRICS_USERNAME", "metricsUsername"), MetricsPassword: envString("METRICS_PASSWORD", "metricsPassword"), Port: envInt("PORT", "port", 8080), SentryDSN: envSecretString("SENTRY_DSN", "sentryDSN"), log: log, params: ¶ms, } // Set default DataDir based on environment if s.DataDir == "" { if s.IsProd() { s.DataDir = "/data/events" } else { s.DataDir = "./data" } } // Validate database URL if s.DBURL == "" { return nil, fmt.Errorf("database URL (DBURL) is required") } if s.Debug { params.Logger.EnableDebugLogging() } // Log configuration summary (without secrets) log.Info("Configuration loaded", "environment", s.Environment, "port", s.Port, "debug", s.Debug, "maintenanceMode", s.MaintenanceMode, "developmentMode", s.DevelopmentMode, "dataDir", s.DataDir, "hasSentryDSN", s.SentryDSN != "", "hasMetricsAuth", s.MetricsUsername != "" && s.MetricsPassword != "", ) return s, nil }