mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"bookra/apps/backend/internal/shared"
|
|
)
|
|
|
|
type Config struct {
|
|
Environment string
|
|
Port string
|
|
APIURL string
|
|
FrontendURL string
|
|
DatabaseURL string
|
|
DatabaseDirectURL string
|
|
NeonAuthURL string
|
|
AuthJWTSecret string
|
|
JobRunnerKey string
|
|
EmailFrom string
|
|
SMTPHost string
|
|
SMTPPort string
|
|
SMTPUsername string
|
|
SMTPPassword string
|
|
PaddleEnvironment string
|
|
PaddleAPIKey string
|
|
PaddleWebhookKey string
|
|
PaddlePriceMatrix map[string]map[string]string
|
|
UmamiAPIURL string
|
|
UmamiAPIKey string
|
|
DemoMode bool
|
|
}
|
|
|
|
func Load() (Config, error) {
|
|
cfg := Config{
|
|
Environment: valueOrDefault("BOOKRA_APP_ENV", "development"),
|
|
Port: valueOrDefault("BOOKRA_API_PORT", "8080"),
|
|
APIURL: valueOrDefault("BOOKRA_API_URL", "http://localhost:8080"),
|
|
FrontendURL: valueOrDefault("BOOKRA_FRONTEND_URL", "http://localhost:3000"),
|
|
DatabaseURL: strings.TrimSpace(os.Getenv("BOOKRA_DATABASE_URL")),
|
|
DatabaseDirectURL: strings.TrimSpace(os.Getenv("BOOKRA_DATABASE_DIRECT_URL")),
|
|
NeonAuthURL: strings.TrimSpace(os.Getenv("BOOKRA_NEON_AUTH_URL")),
|
|
AuthJWTSecret: strings.TrimSpace(os.Getenv("BOOKRA_AUTH_JWT_SECRET")),
|
|
JobRunnerKey: strings.TrimSpace(os.Getenv("BOOKRA_JOB_RUNNER_KEY")),
|
|
EmailFrom: valueOrDefault("BOOKRA_EMAIL_FROM", "noreply@bookra.dev"),
|
|
SMTPHost: strings.TrimSpace(os.Getenv("BOOKRA_SMTP_HOST")),
|
|
SMTPPort: valueOrDefault("BOOKRA_SMTP_PORT", "587"),
|
|
SMTPUsername: strings.TrimSpace(os.Getenv("BOOKRA_SMTP_USERNAME")),
|
|
SMTPPassword: strings.TrimSpace(os.Getenv("BOOKRA_SMTP_PASSWORD")),
|
|
PaddleEnvironment: normalizePaddleEnvironment(os.Getenv("BOOKRA_PADDLE_ENV")),
|
|
PaddleAPIKey: strings.TrimSpace(os.Getenv("BOOKRA_PADDLE_API_KEY")),
|
|
PaddleWebhookKey: strings.TrimSpace(os.Getenv("BOOKRA_PADDLE_WEBHOOK_SECRET")),
|
|
PaddlePriceMatrix: paddlePriceMatrixFromEnv(),
|
|
UmamiAPIURL: strings.TrimSpace(os.Getenv("BOOKRA_UMAMI_API_URL")),
|
|
UmamiAPIKey: strings.TrimSpace(os.Getenv("BOOKRA_UMAMI_API_KEY")),
|
|
DemoMode: boolFromEnv("BOOKRA_DEMO_MODE", false),
|
|
}
|
|
|
|
if cfg.FrontendURL == "" {
|
|
return Config{}, errors.New("BOOKRA_FRONTEND_URL is required")
|
|
}
|
|
if err := cfg.validateRuntimeRequirements(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (cfg Config) validateRuntimeRequirements() error {
|
|
if cfg.Environment == "development" || cfg.Environment == "test" {
|
|
return nil
|
|
}
|
|
|
|
missing := make([]string, 0, 3)
|
|
if cfg.DatabaseURL == "" {
|
|
missing = append(missing, "BOOKRA_DATABASE_URL")
|
|
}
|
|
if cfg.NeonAuthURL == "" {
|
|
missing = append(missing, "BOOKRA_NEON_AUTH_URL")
|
|
}
|
|
if cfg.JobRunnerKey == "" {
|
|
missing = append(missing, "BOOKRA_JOB_RUNNER_KEY")
|
|
}
|
|
if cfg.SMTPHost == "" {
|
|
missing = append(missing, "BOOKRA_SMTP_HOST")
|
|
}
|
|
if cfg.PaddleAPIKey == "" {
|
|
missing = append(missing, "BOOKRA_PADDLE_API_KEY")
|
|
}
|
|
if cfg.PaddleWebhookKey == "" {
|
|
missing = append(missing, "BOOKRA_PADDLE_WEBHOOK_SECRET")
|
|
}
|
|
for _, planCode := range []string{"starter", "pro", "business"} {
|
|
if cfg.PaddlePriceMatrix[planCode]["czk"] == "" || cfg.PaddlePriceMatrix[planCode]["usd"] == "" {
|
|
envPlan := strings.ToUpper(strings.ReplaceAll(planCode, "-", "_"))
|
|
missing = append(missing, "BOOKRA_PADDLE_"+envPlan+"_CZK_PRICE_ID")
|
|
missing = append(missing, "BOOKRA_PADDLE_"+envPlan+"_USD_PRICE_ID")
|
|
}
|
|
}
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("%s required when BOOKRA_APP_ENV=%s", strings.Join(uniqueStrings(missing), ", "), cfg.Environment)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (cfg Config) PaddleConfigured() bool {
|
|
return strings.TrimSpace(cfg.PaddleAPIKey) != ""
|
|
}
|
|
|
|
func (cfg Config) PaddleWebhookConfigured() bool {
|
|
return strings.TrimSpace(cfg.PaddleWebhookKey) != ""
|
|
}
|
|
|
|
func (cfg Config) PaddleCheckoutConfigured(planCode string) bool {
|
|
planCode = shared.NormalizePlanCode(planCode)
|
|
return cfg.PaddleConfigured() && cfg.PaddleWebhookConfigured() && cfg.PaddlePriceMatrix[planCode]["czk"] != "" && cfg.PaddlePriceMatrix[planCode]["usd"] != ""
|
|
}
|
|
|
|
func paddlePriceMatrixFromEnv() map[string]map[string]string {
|
|
matrix := map[string]map[string]string{
|
|
"starter": {},
|
|
"pro": {},
|
|
"business": {},
|
|
}
|
|
for _, planCode := range []string{"starter", "pro", "business"} {
|
|
envPlan := strings.ToUpper(strings.ReplaceAll(planCode, "-", "_"))
|
|
matrix[planCode]["czk"] = strings.TrimSpace(os.Getenv("BOOKRA_PADDLE_" + envPlan + "_CZK_PRICE_ID"))
|
|
matrix[planCode]["usd"] = strings.TrimSpace(os.Getenv("BOOKRA_PADDLE_" + envPlan + "_USD_PRICE_ID"))
|
|
}
|
|
return matrix
|
|
}
|
|
|
|
func normalizePaddleEnvironment(value string) string {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "live", "production":
|
|
return "live"
|
|
default:
|
|
return "sandbox"
|
|
}
|
|
}
|
|
|
|
func valueOrDefault(key string, fallback string) string {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func boolFromEnv(key string, fallback bool) bool {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
value = strings.ToLower(value)
|
|
return value == "true" || value == "1" || value == "yes" || value == "on"
|
|
}
|
|
|
|
func uniqueStrings(values []string) []string {
|
|
seen := map[string]struct{}{}
|
|
out := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
if _, ok := seen[value]; ok {
|
|
continue
|
|
}
|
|
seen[value] = struct{}{}
|
|
out = append(out, value)
|
|
}
|
|
return out
|
|
}
|