package config import ( "errors" "fmt" "os" "strconv" ) type Config struct { HTTPAddr string DatabaseURL string RedisAddr string RedisPassword string ImportPath string DemoUsername string DemoPassword string SessionSecret string CursorSecret string FrontendOrigin string CookieSecure bool } func Load() (Config, error) { cfg := Config{ HTTPAddr: value("HTTP_ADDR", "127.0.0.1:8080"), DatabaseURL: os.Getenv("DATABASE_URL"), RedisAddr: value("REDIS_ADDR", "localhost:6379"), RedisPassword: os.Getenv("REDIS_PASSWORD"), ImportPath: value("IMPORT_PATH", "data/incoming/challenge_data.csv.gz"), DemoUsername: value("DEMO_USERNAME", "merchant-0001"), DemoPassword: os.Getenv("DEMO_PASSWORD"), SessionSecret: os.Getenv("SESSION_SECRET"), CursorSecret: os.Getenv("CURSOR_SECRET"), FrontendOrigin: value("FRONTEND_ORIGIN", "http://localhost:5173"), } var err error cfg.CookieSecure, err = strconv.ParseBool(value("COOKIE_SECURE", "false")) if err != nil { return Config{}, fmt.Errorf("parsing cookie secure: %w", err) } if cfg.DatabaseURL == "" { return Config{}, errors.New("config: DATABASE_URL is required") } return cfg, nil } func (c Config) ValidateAPI() error { if len(c.SessionSecret) < 32 { return errors.New("config: SESSION_SECRET must contain at least 32 characters") } if len(c.CursorSecret) < 32 { return errors.New("config: CURSOR_SECRET must contain at least 32 characters") } if c.CursorSecret == c.SessionSecret { return errors.New("config: CURSOR_SECRET must differ from SESSION_SECRET") } return nil } func (c Config) ValidateSeed() error { if len(c.DemoPassword) < 12 { return errors.New("config: DEMO_PASSWORD must contain at least 12 characters") } return nil } func value(name, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback }