Files
nexarch/internal/config/config.go
T
sysopsandClaude Sonnet 5 5915a4e7b1 TEN-02: tenant-onboarding-api-self-service
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>
2026-08-27 18:16:54 +02:00

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
}