OnboardingService.Onboard: ein API-Aufruf legt Tenant (via bestehendem Provisioner) UND ersten Administrator-Account (via IAM-01 TenantUserStore) an. CREATE DATABASE erlaubt keine echte cross-database Transaktion, daher Saga-Kompensation: schlaegt die Admin-Anlage nach erfolgreichem Provisioning fehl, wird der Tenant per neuem Provisioner.Deprovision wieder vollstaendig entfernt (Registry.Delete + DB-Drop). Provisioner.Provision mappt Duplikat-Faelle (42P04 duplicate_database und den bei echt parallelen CREATE DATABASE moeglichen 23505-Unique-Konflikt auf pg_database) jetzt auf ErrTenantExists statt einer rohen Postgres-Fehlermeldung. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Onboarding-Vorgang zweimal parallel mit gleichem Slug ausgeloest — TestOnboarding_RejectsDuplicateSlugConcurrently: genau ein Erfolg, kein Doppel-Tenant, zweiter Aufruf bekommt ErrTenantExists. PASS. 2. Fehleingaben (leere Pflichtfelder, ungueltige E-Mail, ungueltiger Slug) — TestOnboarding_ValidationErrors deckt alle vier Faelle mit klaren Fehlern ab, bevor irgendein DB-Zugriff stattfindet. PASS. 3. Erfolgreicher Durchlauf von zweiter Person end-to-end nachvollzogen — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// Config holds startup configuration for the core module.
|
|
// RegistryDSN points at the control-plane registry DB (tenant list,
|
|
// connection info, superadmin accounts) — see nexarch-state.json
|
|
// multi_tenancy: Modell C (physisch getrennte DB pro Mandant).
|
|
type Config struct {
|
|
ListenAddr string
|
|
// RegistryDSN verbindet zur Control-Plane-Registry-Datenbank.
|
|
RegistryDSN string
|
|
// AdminDSN verbindet zur Wartungsdatenbank (z.B. "postgres") und wird nur
|
|
// fuer CREATE/DROP DATABASE beim Tenant-Provisioning verwendet.
|
|
AdminDSN string
|
|
// TenantDSNTemplate enthaelt genau ein "%s" als Platzhalter fuer den
|
|
// Datenbanknamen einer neu provisionierten Tenant-Datenbank.
|
|
TenantDSNTemplate string
|
|
// TenantSchemaPath zeigt auf migrations/tenant/0001_users.up.sql, mit der
|
|
// TEN-02 eine frisch provisionierte Tenant-Datenbank initialisiert.
|
|
TenantSchemaPath string
|
|
}
|
|
|
|
func Load() (Config, error) {
|
|
dsn := os.Getenv("NEXARCH_REGISTRY_DSN")
|
|
if dsn == "" {
|
|
return Config{}, fmt.Errorf("NEXARCH_REGISTRY_DSN not set")
|
|
}
|
|
|
|
adminDSN := os.Getenv("NEXARCH_ADMIN_DSN")
|
|
if adminDSN == "" {
|
|
return Config{}, fmt.Errorf("NEXARCH_ADMIN_DSN not set")
|
|
}
|
|
|
|
dsnTemplate := os.Getenv("NEXARCH_TENANT_DSN_TEMPLATE")
|
|
if dsnTemplate == "" {
|
|
return Config{}, fmt.Errorf("NEXARCH_TENANT_DSN_TEMPLATE not set")
|
|
}
|
|
|
|
schemaPath := os.Getenv("NEXARCH_TENANT_SCHEMA_PATH")
|
|
if schemaPath == "" {
|
|
return Config{}, fmt.Errorf("NEXARCH_TENANT_SCHEMA_PATH not set")
|
|
}
|
|
|
|
addr := os.Getenv("NEXARCH_LISTEN_ADDR")
|
|
if addr == "" {
|
|
addr = ":8080"
|
|
}
|
|
|
|
return Config{
|
|
ListenAddr: addr,
|
|
RegistryDSN: dsn,
|
|
AdminDSN: adminDSN,
|
|
TenantDSNTemplate: dsnTemplate,
|
|
TenantSchemaPath: schemaPath,
|
|
}, nil
|
|
}
|