- dms/ai/workflow: qa0x-Pakete nach internal/gates/<thema> verschoben, Paketname = Thema
- internal/migration{acl,audit,...} -> internal/migration/{acl,audit,...}
- Importpfade, Gate-Skripte und zwei relative Migrationspfade angepasst
- keine Logikaenderung; mail-Gates bewusst unveraendert (pfadabhaengig)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
package migrationrole
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/rbac"
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
|
)
|
|
|
|
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 weist jedem freigegebenen role_migration_map-Eintrag die ueber
|
|
// CoreRole berechnete Core-Rolle zu, in der zugehoerigen Tenant-Datenbank.
|
|
type Migrator struct {
|
|
mapStore *MapStore
|
|
registry *tenant.Registry
|
|
openPool PoolOpener
|
|
|
|
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{},
|
|
}
|
|
}
|
|
|
|
func (m *Migrator) WithPoolOpener(opener PoolOpener) *Migrator {
|
|
m.openPool = opener
|
|
return m
|
|
}
|
|
|
|
// Assignment fasst eine einzelne, ausgefuehrte Rollenzuweisung zusammen.
|
|
type Assignment struct {
|
|
UserMigrationMapID int64
|
|
NexarchUserID string
|
|
SourceSystem string
|
|
SourceRole string
|
|
NexarchRole rbac.Role
|
|
}
|
|
|
|
// Result fasst den Ausgang eines Migrationslaufs zusammen.
|
|
type Result struct {
|
|
Assigned []Assignment
|
|
}
|
|
|
|
// MigrateApproved liest die freigegebene Rollen-Map und weist jedem Eintrag
|
|
// die gemappte Core-Rolle in der passenden Tenant-Datenbank zu
|
|
// (Akzeptanzkriterien 1/2). Ein unbekannter source_role-Wert bricht den
|
|
// gesamten Lauf mit ErrUnmappedRole ab, statt den Benutzer ohne Rolle oder
|
|
// mit einer geratenen Rolle zu migrieren (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 {
|
|
role, err := CoreRole(e.SourceSystem, strings.ToLower(strings.TrimSpace(e.SourceRole)))
|
|
if err != nil {
|
|
return res, fmt.Errorf("role-map-eintrag %d (benutzer %s): %w", e.ID, e.NexarchUserID, err)
|
|
}
|
|
|
|
pool, err := m.tenantPool(ctx, e.NexarchTenantID)
|
|
if err != nil {
|
|
return res, fmt.Errorf("tenant-verbindung fuer role-map-eintrag %d: %w", e.ID, err)
|
|
}
|
|
|
|
grantedBy := e.GrantedBy
|
|
if grantedBy == "" && e.ApprovedBy != nil {
|
|
grantedBy = *e.ApprovedBy
|
|
}
|
|
if grantedBy == "" {
|
|
grantedBy = "cor-03-migration"
|
|
}
|
|
|
|
store := rbac.NewStore(pool)
|
|
if _, err := store.Assign(ctx, e.NexarchUserID, role, grantedBy); err != nil {
|
|
return res, fmt.Errorf("rolle fuer benutzer %s (role-map-eintrag %d) zuweisen: %w", e.NexarchUserID, e.ID, err)
|
|
}
|
|
|
|
res.Assigned = append(res.Assigned, Assignment{
|
|
UserMigrationMapID: e.UserMigrationMapID,
|
|
NexarchUserID: e.NexarchUserID,
|
|
SourceSystem: e.SourceSystem,
|
|
SourceRole: e.SourceRole,
|
|
NexarchRole: role,
|
|
})
|
|
}
|
|
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{}
|
|
}
|