forked from baron/baron-sso
custom claim 타입보정 UI. 대표테넌트 노출 보정
This commit is contained in:
@@ -14,7 +14,9 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -51,6 +53,10 @@ func SeedTenants(db *gorm.DB) error {
|
||||
return errors.New("seed tenant csv has no tenant rows")
|
||||
}
|
||||
|
||||
if err := syncExistingSeedTenantConfigs(db, configs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingSlugs, existingIDs, err := loadExistingTenantIdentitySet(db)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -71,6 +77,69 @@ func SeedTenants(db *gorm.DB) error {
|
||||
return seedTenantConfigs(db, missingConfigs)
|
||||
}
|
||||
|
||||
func syncExistingSeedTenantConfigs(db *gorm.DB, configs []InitialTenantConfig) error {
|
||||
for _, config := range configs {
|
||||
id := strings.TrimSpace(config.TenantID)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
targetSlug := strings.TrimSpace(strings.ToLower(config.Slug))
|
||||
if targetSlug == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := db.Transaction(func(tx *gorm.DB) error {
|
||||
var tenant domain.Tenant
|
||||
if err := tx.First(&tenant, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("load existing seed tenant %q: %w", id, err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(strings.ToLower(tenant.Slug)) == targetSlug {
|
||||
return nil
|
||||
}
|
||||
|
||||
var conflict domain.Tenant
|
||||
err := tx.Select("id").
|
||||
Where("LOWER(TRIM(slug)) = ? AND id <> ?", targetSlug, tenant.ID).
|
||||
First(&conflict).Error
|
||||
if err == nil {
|
||||
return fmt.Errorf("seed tenant slug %q for id %q conflicts with tenant id %q", targetSlug, id, conflict.ID)
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("check seed tenant slug conflict %q: %w", targetSlug, err)
|
||||
}
|
||||
|
||||
suffix := "-deleted-" + strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
|
||||
if err := tx.Unscoped().
|
||||
Model(&domain.Tenant{}).
|
||||
Where("slug = ? AND id <> ? AND deleted_at IS NOT NULL", targetSlug, tenant.ID).
|
||||
Update("slug", gorm.Expr("slug || ?", suffix)).Error; err != nil {
|
||||
return fmt.Errorf("rename deleted tenant slug %q before seed repair: %w", targetSlug, err)
|
||||
}
|
||||
|
||||
slog.Info(
|
||||
"[Bootstrap] Repairing existing seed tenant slug",
|
||||
"id", tenant.ID,
|
||||
"oldSlug", tenant.Slug,
|
||||
"newSlug", targetSlug,
|
||||
)
|
||||
if err := tx.Model(&domain.Tenant{}).
|
||||
Where("id = ?", tenant.ID).
|
||||
Update("slug", targetSlug).Error; err != nil {
|
||||
return fmt.Errorf("repair seed tenant slug %q for id %q: %w", targetSlug, id, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadExistingTenantIdentitySet(db *gorm.DB) (map[string]bool, map[string]bool, error) {
|
||||
var tenants []domain.Tenant
|
||||
if err := db.Select("id", "slug").Find(&tenants).Error; err != nil {
|
||||
|
||||
@@ -273,7 +273,7 @@ func TestFilterMissingSeedTenantConfigsSkipsExistingSlugs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedTenantsCreatesMissingSeedRowsWithoutTouchingExistingSlugs(t *testing.T) {
|
||||
func TestSeedTenantsCreatesMissingSeedRowsAndRepairsExistingSeedSlug(t *testing.T) {
|
||||
if !testsupport.DockerAvailable() {
|
||||
t.Skip("Docker provider is unavailable in this environment")
|
||||
}
|
||||
@@ -326,18 +326,30 @@ func TestSeedTenantsCreatesMissingSeedRowsWithoutTouchingExistingSlugs(t *testin
|
||||
Type: domain.TenantTypeCompany,
|
||||
Status: domain.TenantStatusActive,
|
||||
}
|
||||
existingSeedTenantWithTypoSlug := domain.Tenant{
|
||||
ID: "5a03efd2-e62f-4243-800d-58334bf48b2f",
|
||||
Name: "한라산업개발",
|
||||
Slug: "hanlla",
|
||||
Type: domain.TenantTypeCompany,
|
||||
Description: "seed tenant with a typo slug must be repaired by UUID",
|
||||
Status: domain.TenantStatusActive,
|
||||
ParentID: &existingRoot.ID,
|
||||
}
|
||||
if err := db.Create(&existingRoot).Error; err != nil {
|
||||
t.Fatalf("failed to create existing root tenant: %v", err)
|
||||
}
|
||||
if err := db.Create(&nonSeedTenant).Error; err != nil {
|
||||
t.Fatalf("failed to create non-seed tenant: %v", err)
|
||||
}
|
||||
if err := db.Create(&existingSeedTenantWithTypoSlug).Error; err != nil {
|
||||
t.Fatalf("failed to create existing seed tenant with typo slug: %v", err)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "seed-tenant.csv")
|
||||
csv := "id,name,type,parent_tenant_slug,slug,memo,email_domain\n" +
|
||||
"10000000-0000-0000-0000-000000000001,Seed Root Name,COMPANY_GROUP,,existing-root,seed must be skipped,\n" +
|
||||
"00000000-0000-0000-0000-000000000002,Conflicting ID,COMPANY,existing-root,conflicting-id,seed id must be skipped,\n" +
|
||||
"5a03efd2-e62f-4243-800d-58334bf48b2f,한라산업개발,COMPANY,existing-root,halla,seed typo slug must be repaired,hallasanup.com\n" +
|
||||
"10000000-0000-0000-0000-000000000002,Missing Child,COMPANY,existing-root,missing-child,created from seed,child.example.com\n"
|
||||
if err := os.WriteFile(path, []byte(csv), 0o600); err != nil {
|
||||
t.Fatalf("failed to write seed csv: %v", err)
|
||||
@@ -359,6 +371,22 @@ func TestSeedTenantsCreatesMissingSeedRowsWithoutTouchingExistingSlugs(t *testin
|
||||
t.Fatalf("existing root name = %q, want untouched %q", root.Name, existingRoot.Name)
|
||||
}
|
||||
|
||||
var manual domain.Tenant
|
||||
if err := db.First(&manual, "id = ?", nonSeedTenant.ID).Error; err != nil {
|
||||
t.Fatalf("failed to load non-seed tenant after seed: %v", err)
|
||||
}
|
||||
if manual.Slug != nonSeedTenant.Slug {
|
||||
t.Fatalf("non-seed tenant slug = %q, want untouched %q", manual.Slug, nonSeedTenant.Slug)
|
||||
}
|
||||
|
||||
var repairedSeed domain.Tenant
|
||||
if err := db.First(&repairedSeed, "id = ?", existingSeedTenantWithTypoSlug.ID).Error; err != nil {
|
||||
t.Fatalf("failed to load existing seed tenant after seed: %v", err)
|
||||
}
|
||||
if repairedSeed.Slug != "halla" {
|
||||
t.Fatalf("existing seed tenant slug = %q, want halla", repairedSeed.Slug)
|
||||
}
|
||||
|
||||
var child domain.Tenant
|
||||
if err := db.Preload("Domains").First(&child, "slug = ?", "missing-child").Error; err != nil {
|
||||
t.Fatalf("missing seed child was not created: %v", err)
|
||||
@@ -378,11 +406,4 @@ func TestSeedTenantsCreatesMissingSeedRowsWithoutTouchingExistingSlugs(t *testin
|
||||
t.Fatalf("existing-root row count = %d, want 1", rootCount)
|
||||
}
|
||||
|
||||
var conflictingIDCount int64
|
||||
if err := db.Model(&domain.Tenant{}).Where("slug = ?", "conflicting-id").Count(&conflictingIDCount).Error; err != nil {
|
||||
t.Fatalf("failed to count conflicting-id rows: %v", err)
|
||||
}
|
||||
if conflictingIDCount != 0 {
|
||||
t.Fatalf("conflicting-id row count = %d, want 0", conflictingIDCount)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user