managarten/services/mana-sync/internal/config/config.go
Till JS ceed8ccd64 feat(mana-sync): per-app billing exemption — Cards bypasses sync gate
mana-sync's billing middleware short-circuited every push/pull with
402 for users without a sync subscription. Cards promises free Sync
in its Phase-1 GUIDELINES, so it shouldn't gate its own users on a
mana-credits subscription it never sells.

Implementation:
  • billing.NewChecker now takes an exemptApps slice. The middleware
    extracts {appId} from the URL path and short-circuits before the
    user lookup if the app is in the set.
  • Configurable via the BILLING_EXEMPT_APPS env var (comma-separated).
  • Set BILLING_EXEMPT_APPS=cards on the mana-sync container so the
    cards.mana.how Sync loop stops 402-ing.
  • Tests cover the exemption + the empty/whitespace edge cases. All
    other apps keep the original behaviour (fail-open if mana-credits
    is unreachable, 402 if it explicitly says inactive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:01:54 +02:00

58 lines
1.7 KiB
Go

package config
import (
"os"
"strconv"
"strings"
)
// Config holds all configuration for the sync server.
type Config struct {
Port int
DatabaseURL string
JWKSUrl string // mana-auth JWKS endpoint for JWT validation
ManaAuthURL string // mana-auth base URL for internal APIs (Space memberships)
CORSOrigins string
ManaCreditsURL string // mana-credits service URL for billing checks
ServiceKey string // Service-to-service auth key
BillingExemptApps []string // appIDs that bypass the sync-subscription billing gate
}
// Load reads configuration from environment variables with sensible defaults.
func Load() *Config {
port, _ := strconv.Atoi(getEnv("PORT", "3050"))
return &Config{
Port: port,
DatabaseURL: getEnv("DATABASE_URL", "postgresql://mana:devpassword@localhost:5432/mana_sync"),
JWKSUrl: getEnv("JWKS_URL", "http://localhost:3001/api/auth/jwks"),
ManaAuthURL: getEnv("MANA_AUTH_URL", "http://localhost:3001"),
CORSOrigins: getEnv("CORS_ORIGINS", "http://localhost:5173,http://localhost:5188"),
ManaCreditsURL: getEnv("MANA_CREDITS_URL", "http://localhost:3061"),
ServiceKey: getEnv("MANA_SERVICE_KEY", "dev-service-key"),
BillingExemptApps: splitCSV(getEnv("BILLING_EXEMPT_APPS", "")),
}
}
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
// splitCSV splits a comma-separated string into a trimmed, non-empty slice.
func splitCSV(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
t := strings.TrimSpace(p)
if t != "" {
out = append(out, t)
}
}
return out
}