package config import ( "errors" "fmt" "os" "strconv" "strings" ) type Config struct { ServiceName string Environment string HTTP HTTPConfig Database DatabaseConfig Auth AuthConfig } type HTTPConfig struct { Address string } type DatabaseConfig struct { DSN string } type AuthConfig struct { JWKSURL string Audience string Issuer string FallbackSecret string ClockSkew int } func Load() (Config, error) { cfg := Config{ ServiceName: getenvDefault("SERVICE_NAME", "platform-projects-core"), Environment: getenvDefault("ENVIRONMENT", "development"), HTTP: HTTPConfig{ Address: getenvDefault("HTTP_ADDRESS", ":8080"), }, Database: DatabaseConfig{ DSN: os.Getenv("DATABASE_DSN"), }, Auth: AuthConfig{ JWKSURL: os.Getenv("AUTH_JWKS_URL"), Audience: os.Getenv("AUTH_AUDIENCE"), Issuer: os.Getenv("AUTH_ISSUER"), FallbackSecret: os.Getenv("AUTH_FALLBACK_SECRET"), ClockSkew: parseIntDefault("AUTH_CLOCK_SKEW_SECONDS", 60), }, } if cfg.Database.DSN == "" { return Config{}, errors.New("DATABASE_DSN is required") } if cfg.Auth.JWKSURL == "" && cfg.Auth.FallbackSecret == "" { return Config{}, errors.New("AUTH_JWKS_URL or AUTH_FALLBACK_SECRET must be provided") } if cfg.Auth.Audience == "" { return Config{}, errors.New("AUTH_AUDIENCE is required") } if cfg.Auth.Issuer == "" { return Config{}, errors.New("AUTH_ISSUER is required") } if err := cfg.Validate(); err != nil { return Config{}, err } return cfg, nil } func getenvDefault(key, fallback string) string { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback } return value } func parseIntDefault(key string, fallback int) int { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback } parsed, err := strconv.Atoi(value) if err != nil { return fallback } return parsed } func (c Config) Validate() error { if c.ServiceName == "" { return fmt.Errorf("service name must not be empty") } if c.HTTP.Address == "" { return fmt.Errorf("http address must not be empty") } return nil }