MapEntry/MapStore und TenantUserStore um die LDAP-Kopplungsfelder erweitert; MigrateApproved uebernimmt sie in die Tenant-Datenbank. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
package migrationuser
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/user"
|
|
)
|
|
|
|
// PoolOpener oeffnet eine Verbindung zu einer Tenant-Datenbank per DSN.
|
|
// Austauschbar fuer Tests, damit kein echter zweiter Netzwerk-Connect-Pfad
|
|
// gebraucht wird.
|
|
type PoolOpener func(ctx context.Context, dsn string) (*pgxpool.Pool, error)
|
|
|
|
func defaultPoolOpener(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
|
return pgxpool.New(ctx, dsn)
|
|
}
|
|
|
|
// Migrator uebernimmt jeden freigegebenen user_migration_map-Eintrag in die
|
|
// zugehoerige Tenant-Datenbank, mit der nexarch_user_id aus der Map als
|
|
// users.id und dem bcrypt-Hash 1:1 (kein Rehash).
|
|
type Migrator struct {
|
|
mapStore *MapStore
|
|
registry *tenant.Registry
|
|
openPool PoolOpener
|
|
|
|
// tenantPools cached geoeffnete Tenant-Pools je nexarch_tenant_id,
|
|
// damit mehrere Benutzer desselben Mandanten nicht wiederholt neu
|
|
// verbinden.
|
|
tenantPools map[string]*pgxpool.Pool
|
|
}
|
|
|
|
func NewMigrator(mapStore *MapStore, registry *tenant.Registry) *Migrator {
|
|
return &Migrator{
|
|
mapStore: mapStore,
|
|
registry: registry,
|
|
openPool: defaultPoolOpener,
|
|
tenantPools: map[string]*pgxpool.Pool{},
|
|
}
|
|
}
|
|
|
|
// WithPoolOpener ersetzt den Standard-Verbindungsaufbau (fuer Tests).
|
|
func (m *Migrator) WithPoolOpener(opener PoolOpener) *Migrator {
|
|
m.openPool = opener
|
|
return m
|
|
}
|
|
|
|
// Result fasst den Ausgang eines Migrationslaufs zusammen.
|
|
type Result struct {
|
|
Migrated []user.User
|
|
}
|
|
|
|
// MigrateApproved liest die freigegebene Benutzer-Map und uebernimmt jeden
|
|
// Eintrag genau einmal in die passende Tenant-Datenbank (Akzeptanzkriterium
|
|
// 1). Ein erneuter Lauf gegen dieselbe Quelle erzeugt keine Duplikate, da
|
|
// TenantUserStore.CreateWithID bei bereits existierender ID unveraendert
|
|
// zurueckkehrt (Akzeptanzkriterium 3).
|
|
func (m *Migrator) MigrateApproved(ctx context.Context) (Result, error) {
|
|
entries, err := m.mapStore.Approved(ctx)
|
|
if err != nil {
|
|
return Result{}, err
|
|
}
|
|
|
|
var res Result
|
|
for _, e := range entries {
|
|
pool, err := m.tenantPool(ctx, e.NexarchTenantID)
|
|
if err != nil {
|
|
return res, fmt.Errorf("tenant-verbindung fuer benutzer %q (map-eintrag %d): %w", e.Email, e.ID, err)
|
|
}
|
|
|
|
store := user.NewTenantUserStore(pool)
|
|
u, err := store.CreateWithIDAndLDAP(ctx, e.NexarchUserID, e.Email, e.Name, e.PasswordHash, e.LDAPUID, e.LDAPSource, e.LDAPSyncedAt)
|
|
if err != nil {
|
|
return res, fmt.Errorf("benutzer %q (map-eintrag %d) uebernehmen: %w", e.Email, e.ID, err)
|
|
}
|
|
res.Migrated = append(res.Migrated, u)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (m *Migrator) tenantPool(ctx context.Context, tenantID string) (*pgxpool.Pool, error) {
|
|
if pool, ok := m.tenantPools[tenantID]; ok {
|
|
return pool, nil
|
|
}
|
|
|
|
t, err := m.registry.GetByID(ctx, tenantID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tenant %q laden: %w", tenantID, err)
|
|
}
|
|
|
|
pool, err := m.openPool(ctx, t.DBDSN)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tenant %q verbinden: %w", tenantID, err)
|
|
}
|
|
m.tenantPools[tenantID] = pool
|
|
return pool, nil
|
|
}
|
|
|
|
// Close schliesst alle im Lauf geoeffneten Tenant-Pools.
|
|
func (m *Migrator) Close() {
|
|
for _, pool := range m.tenantPools {
|
|
pool.Close()
|
|
}
|
|
m.tenantPools = map[string]*pgxpool.Pool{}
|
|
}
|