// Package config loads application configuration from environment variables, // optionally populated from a .env file in the working directory. package config import ( "flag" "fmt" "os" "strconv" "github.com/joho/godotenv" ) type Config struct { DatabaseURL string Auth0Domain string Auth0ClientID string Auth0ClientSecret string Auth0CallbackURL string EtsyAPIKeystring string EtsyAPISharedSecret string // DevAuthEnabled, when true, exposes a route that mints a local // login session for any user_id without going through Auth0. Must // never be true outside local development. DevAuthEnabled bool Port int } var envFlagPtr = flag.String("env", "", "") func init() { flag.Parse() } // Load reads a .env file, if present, into the process environment, then // reads the required configuration values from the environment. func Load() (Config, error) { dev, err := checkEnvFlag() if err != nil { return Config{}, err } if dev { if err := godotenv.Load(".env.dev"); err != nil && !os.IsNotExist(err) { return Config{}, fmt.Errorf("failed to load .env.dev file: %w", err) } } else { if err := godotenv.Load(); err != nil && !os.IsNotExist(err) { return Config{}, fmt.Errorf("failed to load .env file: %w", err) } } var ( cfg Config missing []string ) required := func(key string) string { v := os.Getenv(key) if v == "" { missing = append(missing, key) } return v } cfg.DatabaseURL = required("DATABASE_URL") // DEV_AUTH_ENABLED must be known before the Auth0 vars are read below, // since it decides whether those are required at all. if raw := os.Getenv("DEV_AUTH_ENABLED"); raw != "" { devAuthEnabled, err := strconv.ParseBool(raw) if err != nil { return Config{}, fmt.Errorf("invalid DEV_AUTH_ENABLED value %q: %w", raw, err) } cfg.DevAuthEnabled = devAuthEnabled } // With DEV_AUTH_ENABLED, real Auth0 login/logout never runs (dev-login // mints sessions locally instead), so these are unused and optional. if cfg.DevAuthEnabled { cfg.Auth0Domain = os.Getenv("AUTH0_DOMAIN") cfg.Auth0ClientID = os.Getenv("AUTH0_CLIENT_ID") cfg.Auth0ClientSecret = os.Getenv("AUTH0_CLIENT_SECRET") cfg.Auth0CallbackURL = os.Getenv("AUTH0_CALLBACK_URL") } else { cfg.Auth0Domain = required("AUTH0_DOMAIN") cfg.Auth0ClientID = required("AUTH0_CLIENT_ID") cfg.Auth0ClientSecret = required("AUTH0_CLIENT_SECRET") cfg.Auth0CallbackURL = required("AUTH0_CALLBACK_URL") } cfg.EtsyAPIKeystring = required("ETSY_API_KEYSTRING") cfg.EtsyAPISharedSecret = required("ETSY_API_SHARED_SECRET") portStr := required("PORT") if len(missing) > 0 { return Config{}, fmt.Errorf( "missing required environment variables: %v (see .env.example)", missing, ) } if port, err := strconv.Atoi(portStr); err != nil { return Config{}, fmt.Errorf("invalid port number: %w", err) } else { cfg.Port = port } return cfg, nil } func checkEnvFlag() (dev bool, err error) { switch env := *envFlagPtr; env { case "dev": return true, nil case "prd": return false, nil case "": return false, fmt.Errorf("no env flag specified: must be one of dev, prd") default: return false, fmt.Errorf("invalid env flag specified: must be one of dev, prd: %q", env) } }