package config import ( "fmt" "os" "strconv" "strings" "time" ) type Config struct { AppName string Environment string APIPort string DatabaseURL string StorefrontURL string AllowedOrigins []string ResendAPIKey string ResendOrderFrom string ResendForward string ClientDashboardUser string ClientDashboardPass string ClientDashboardSecret string ClientDashboardSessionTTL time.Duration StripeSecretKey string StripeWebhookSecret string StripePriceID string StripeCurrency string RoyalPopUnitAmount int64 RoyalPopRetailAmount int64 ShutdownTimeout time.Duration } func Load() *Config { environment := getEnv("GO_ENV", "development") storefrontURL := getEnv("API_ALLOWED_ORIGIN", getEnv("STOREFRONT_URL", "http://localhost:4321")) clientDashboardUser := getEnv("CLIENT_DASHBOARD_USERNAME", "") clientDashboardPass := getEnv("CLIENT_DASHBOARD_PASSWORD", "") if strings.EqualFold(environment, "development") { if strings.TrimSpace(clientDashboardUser) == "" { clientDashboardUser = "client" } if strings.TrimSpace(clientDashboardPass) == "" { clientDashboardPass = "royalpop-dev" } } return &Config{ AppName: getEnv("APP_NAME", "royal-pop"), Environment: environment, APIPort: getEnv("BACKEND_API_PORT", "8081"), DatabaseURL: getEnv("DATABASE_URL", "postgres://royalpop:royalpop_dev_password@localhost:5432/royalpop?sslmode=disable"), StorefrontURL: storefrontURL, AllowedOrigins: splitCSV(getEnv("API_ALLOWED_ORIGINS", storefrontURL)), ResendAPIKey: getEnv("RESEND_API_KEY", ""), ResendOrderFrom: getEnv("RESEND_ORDER_FROM", "orders@royal-pop-accessory.com"), ResendForward: getEnv("RESEND_FORWARD", ""), ClientDashboardUser: clientDashboardUser, ClientDashboardPass: clientDashboardPass, ClientDashboardSecret: getEnv("CLIENT_DASHBOARD_SESSION_SECRET", ""), ClientDashboardSessionTTL: getDurationEnv("CLIENT_DASHBOARD_SESSION_TTL", 12*time.Hour), StripeSecretKey: getEnv("STRIPE_SECRET_KEY", ""), StripeWebhookSecret: getEnv("STRIPE_WEBHOOK_SECRET", ""), StripePriceID: getEnv("STRIPE_PRICE_ID", "price_1TkPnvCpoCwKMSycHiQBPVms"), StripeCurrency: strings.ToLower(getEnv("STRIPE_CURRENCY", "gbp")), RoyalPopUnitAmount: getInt64Env("ROYAL_POP_UNIT_AMOUNT", 4999), RoyalPopRetailAmount: getInt64Env("ROYAL_POP_RETAIL_AMOUNT", 8999), ShutdownTimeout: getDurationEnv("BACKEND_SHUTDOWN_TIMEOUT", 10*time.Second), } } func (c *Config) Address() string { return fmt.Sprintf(":%s", c.APIPort) } func (c *Config) IsDevelopment() bool { return strings.EqualFold(c.Environment, "development") } func (c *Config) ClientDashboardEnabled() bool { if c == nil { return false } return strings.TrimSpace(c.ClientDashboardUser) != "" && strings.TrimSpace(c.ClientDashboardPass) != "" } func (c *Config) ClientDashboardSigningSecret() string { if c == nil || !c.ClientDashboardEnabled() { return "" } if secret := strings.TrimSpace(c.ClientDashboardSecret); secret != "" { return secret } return strings.Join([]string{c.AppName, c.ClientDashboardUser, c.ClientDashboardPass}, "|") } func getEnv(key, fallback string) string { if value, exists := os.LookupEnv(key); exists && strings.TrimSpace(value) != "" { return value } return fallback } func getDurationEnv(key string, fallback time.Duration) time.Duration { value, exists := os.LookupEnv(key) if !exists { return fallback } parsed, err := time.ParseDuration(value) if err != nil { return fallback } return parsed } func getInt64Env(key string, fallback int64) int64 { value, exists := os.LookupEnv(key) if !exists || strings.TrimSpace(value) == "" { return fallback } parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64) if err != nil { return fallback } return parsed } func splitCSV(raw string) []string { parts := strings.Split(raw, ",") values := make([]string, 0, len(parts)) for _, part := range parts { trimmed := strings.TrimSpace(part) if trimmed == "" { continue } values = append(values, trimmed) } if len(values) == 0 { return []string{"*"} } return values }