6290475ea3
Kommerzielle Hosting-/Abo-Plattform für Rapport-Instanzen. - React-Frontend (Vite/JSX): Landing, Register, Login, Plans, Dashboard - Node/Express-Backend: Auth (bcrypt+JWT), Stripe-Billing, Provisioning - HOST-Postgres-Schema: accounts, subscriptions, instances - Provisioning-Interface + Modell-A-Adapter (Studio im geteilten Stack) - MOCK-Modus: voller End-to-End-Flow ohne Stripe/Rapport-Stack testbar - Idempotentes Fulfillment (Upsert auf stripe_subscription_id) - docker-compose für lokale host-db; identisch auf Hetzner deploybar E2E lokal verifiziert: Register -> Checkout(mock) -> Instanz -> Idempotenz. Lizenz: proprietär (kein AGPL-Code eingebunden, nur Netzwerk-API zur Familie). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
2.2 KiB
JavaScript
57 lines
2.2 KiB
JavaScript
// Zentrales Laden + Validieren der Umgebungsvariablen.
|
|
// Liest .env (einfacher Parser, keine Dependency) und fällt sonst auf
|
|
// process.env zurück — so läuft es lokal mit Datei und auf Hetzner mit
|
|
// echten Env-Vars, ohne Code-Änderung.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const envPath = path.resolve(__dirname, "..", ".env");
|
|
|
|
if (fs.existsSync(envPath)) {
|
|
for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
|
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
|
|
if (!m) continue;
|
|
const key = m[1];
|
|
let val = m[2].replace(/^["']|["']$/g, "");
|
|
if (process.env[key] === undefined) process.env[key] = val;
|
|
}
|
|
}
|
|
|
|
const e = process.env;
|
|
|
|
export const env = {
|
|
port: parseInt(e.PORT || "8787", 10),
|
|
publicBaseUrl: (e.PUBLIC_BASE_URL || "http://localhost:5273").replace(/\/+$/, ""),
|
|
jwtSecret: e.JWT_SECRET || "dev-insecure-secret-change-me",
|
|
databaseUrl: e.DATABASE_URL || "postgres://rapport_host:rapport_host@localhost:55432/rapport_host",
|
|
|
|
stripe: {
|
|
secretKey: e.STRIPE_SECRET_KEY || "",
|
|
webhookSecret: e.STRIPE_WEBHOOK_SECRET || "",
|
|
prices: {
|
|
solo: e.STRIPE_PRICE_SOLO || "",
|
|
studio: e.STRIPE_PRICE_STUDIO || "",
|
|
business: e.STRIPE_PRICE_BUSINESS || "",
|
|
},
|
|
},
|
|
|
|
rapport: {
|
|
apiUrl: e.RAPPORT_API_URL || "",
|
|
serviceKey: e.RAPPORT_SERVICE_KEY || "",
|
|
instanceUrlTemplate: e.RAPPORT_INSTANCE_URL_TEMPLATE || "http://localhost:8080/?studio={slug}",
|
|
},
|
|
};
|
|
|
|
// MOCK-Modus: ohne echte Stripe-/Rapport-Keys läuft alles lokal simuliert,
|
|
// damit der End-to-End-Flow ohne externe Dienste testbar ist.
|
|
export const stripeEnabled = !!env.stripe.secretKey && env.stripe.secretKey.startsWith("sk_");
|
|
export const provisioningMock = !env.rapport.apiUrl || !env.rapport.serviceKey;
|
|
|
|
if (env.jwtSecret === "dev-insecure-secret-change-me") {
|
|
console.warn("⚠ JWT_SECRET nicht gesetzt — unsicheres Dev-Secret in Verwendung.");
|
|
}
|
|
if (!stripeEnabled) console.warn("⚠ STRIPE_SECRET_KEY fehlt — Billing läuft im MOCK-Modus.");
|
|
if (provisioningMock) console.warn("⚠ RAPPORT_API_URL/SERVICE_KEY fehlen — Provisioning läuft im MOCK-Modus.");
|