mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
259 lines
9.1 KiB
Go
259 lines
9.1 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
|
|
StripeAPIKey string
|
|
StripeWebhookKey string
|
|
StripePriceMatrix map[string]map[string]string
|
|
AdminEmail string
|
|
AdminKey string
|
|
UmamiAPIURL string
|
|
UmamiAPIKey string
|
|
SentryDSN string
|
|
DemoMode bool
|
|
SMSManagerAPIKey string
|
|
SMSManagerBaseURL string
|
|
StripeSMSPriceMatrix map[string]string // currency -> price ID (czk, usd, eur, gbp, ...)
|
|
}
|
|
|
|
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(),
|
|
StripeAPIKey: strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_API_KEY")),
|
|
StripeWebhookKey: strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_WEBHOOK_SECRET")),
|
|
StripePriceMatrix: stripePriceMatrixFromEnv(),
|
|
AdminEmail: strings.TrimSpace(os.Getenv("BOOKRA_ADMIN_EMAIL")),
|
|
AdminKey: strings.TrimSpace(os.Getenv("BOOKRA_ADMIN_KEY")),
|
|
UmamiAPIURL: strings.TrimSpace(os.Getenv("BOOKRA_UMAMI_API_URL")),
|
|
UmamiAPIKey: strings.TrimSpace(os.Getenv("BOOKRA_UMAMI_API_KEY")),
|
|
SentryDSN: strings.TrimSpace(os.Getenv("BOOKRA_SENTRY_DSN")),
|
|
DemoMode: boolFromEnv("BOOKRA_DEMO_MODE", false),
|
|
SMSManagerAPIKey: strings.TrimSpace(os.Getenv("BOOKRA_SMSMANAGER_API_KEY")),
|
|
SMSManagerBaseURL: valueOrDefault("BOOKRA_SMSMANAGER_BASE_URL", "https://api.smsmngr.com/v2"),
|
|
StripeSMSPriceMatrix: smsPriceMatrixFromEnv(),
|
|
}
|
|
|
|
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 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 (cfg Config) StripeConfigured() bool {
|
|
return strings.TrimSpace(cfg.StripeAPIKey) != ""
|
|
}
|
|
|
|
func (cfg Config) StripeWebhookConfigured() bool {
|
|
return strings.TrimSpace(cfg.StripeWebhookKey) != ""
|
|
}
|
|
|
|
func (cfg Config) StripeCheckoutConfigured(planCode string) bool {
|
|
planCode = shared.NormalizePlanCode(planCode)
|
|
return cfg.StripeConfigured() && cfg.StripeWebhookConfigured() && cfg.StripePriceMatrix[planCode]["czk"] != "" && cfg.StripePriceMatrix[planCode]["usd"] != ""
|
|
}
|
|
|
|
func (cfg Config) BillingProvider() string {
|
|
if cfg.StripeConfigured() {
|
|
return "stripe"
|
|
}
|
|
return "paddle"
|
|
}
|
|
|
|
func (cfg Config) BillingConfigured() bool {
|
|
return cfg.StripeConfigured() || cfg.PaddleConfigured()
|
|
}
|
|
|
|
func (cfg Config) BillingWebhookConfigured() bool {
|
|
return cfg.StripeWebhookConfigured() || cfg.PaddleWebhookConfigured()
|
|
}
|
|
|
|
func (cfg Config) SMSConfigured() bool {
|
|
return strings.TrimSpace(cfg.SMSManagerAPIKey) != ""
|
|
}
|
|
|
|
func (cfg Config) StripeSMSConfigured() bool {
|
|
return cfg.StripeConfigured() && cfg.StripeSMSPriceMatrix["czk"] != ""
|
|
}
|
|
|
|
func (cfg Config) StripeSMSPriceID(currency string) string {
|
|
c := strings.ToLower(strings.TrimSpace(currency))
|
|
if c == "" {
|
|
c = "czk"
|
|
}
|
|
if id := cfg.StripeSMSPriceMatrix[c]; id != "" {
|
|
return id
|
|
}
|
|
return cfg.StripeSMSPriceMatrix["czk"]
|
|
}
|
|
|
|
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 stripePriceMatrixFromEnv() 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, "-", "_"))
|
|
|
|
// Monthly prices
|
|
matrix[planCode][planCode+":czk:monthly"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_CZK_MONTHLY_PRICE_ID"))
|
|
matrix[planCode][planCode+":usd:monthly"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_USD_MONTHLY_PRICE_ID"))
|
|
matrix[planCode][planCode+":czk"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_CZK_PRICE_ID"))
|
|
matrix[planCode][planCode+":usd"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_USD_PRICE_ID"))
|
|
matrix[planCode]["czk"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_CZK_PRICE_ID"))
|
|
matrix[planCode]["usd"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_USD_PRICE_ID"))
|
|
|
|
// Yearly prices
|
|
matrix[planCode][planCode+":czk:yearly"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_CZK_YEARLY_PRICE_ID"))
|
|
matrix[planCode][planCode+":usd:yearly"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_USD_YEARLY_PRICE_ID"))
|
|
matrix[planCode]["yearly:czk"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_CZK_YEARLY_PRICE_ID"))
|
|
matrix[planCode]["yearly:usd"] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_" + envPlan + "_USD_YEARLY_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 smsPriceMatrixFromEnv() map[string]string {
|
|
matrix := map[string]string{}
|
|
for _, currency := range []string{"czk", "usd", "eur", "gbp", "pln"} {
|
|
upper := strings.ToUpper(currency)
|
|
matrix[currency] = strings.TrimSpace(os.Getenv("BOOKRA_STRIPE_SMS_" + upper + "_PRICE_ID"))
|
|
}
|
|
return matrix
|
|
}
|
|
|
|
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
|
|
}
|