1
0
forked from baron/baron-sso

custom claim 타입보정 UI. 대표테넌트 노출 보정

This commit is contained in:
2026-06-11 11:27:11 +09:00
parent 0bb3ccb850
commit f60b15a17b
37 changed files with 2952 additions and 417 deletions

View File

@@ -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 {

View File

@@ -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)
}
}