forked from baron/baron-sso
테넌트 CSV 조직 설정 동기화 보완
This commit is contained in:
@@ -110,6 +110,8 @@ type tenantCSVRecord struct {
|
||||
Slug string
|
||||
Memo string
|
||||
Domains []string
|
||||
Visibility string
|
||||
OrgUnitType string
|
||||
}
|
||||
|
||||
func (h *TenantHandler) RegisterTenantPublic(c *fiber.Ctx) error {
|
||||
@@ -278,10 +280,10 @@ func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error {
|
||||
writer := csv.NewWriter(&buf)
|
||||
includeIDs := includeCSVIds(c)
|
||||
if includeIDs {
|
||||
if err := writer.Write([]string{"tenant_id", "name", "type", "parent_tenant_id", "parent_tenant_slug", "slug", "memo", "email_domain"}); err != nil {
|
||||
if err := writer.Write([]string{"tenant_id", "name", "type", "parent_tenant_id", "parent_tenant_slug", "slug", "memo", "email_domain", "visibility", "org_unit_type"}); err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
} else if err := writer.Write([]string{"name", "type", "parent_tenant_slug", "slug", "memo", "email_domain"}); err != nil {
|
||||
} else if err := writer.Write([]string{"name", "type", "parent_tenant_slug", "slug", "memo", "email_domain", "visibility", "org_unit_type"}); err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
slugByID := make(map[string]string, len(tenants))
|
||||
@@ -302,6 +304,7 @@ func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error {
|
||||
domains = append(domains, domainName)
|
||||
}
|
||||
}
|
||||
visibility, orgUnitType := tenantCSVOrgConfigValues(tenant.Config)
|
||||
row := []string{
|
||||
tenant.Name,
|
||||
tenant.Type,
|
||||
@@ -309,6 +312,8 @@ func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error {
|
||||
tenant.Slug,
|
||||
tenant.Description,
|
||||
strings.Join(domains, ";"),
|
||||
visibility,
|
||||
orgUnitType,
|
||||
}
|
||||
if includeIDs {
|
||||
row = []string{
|
||||
@@ -320,6 +325,8 @@ func (h *TenantHandler) ExportTenantsCSV(c *fiber.Ctx) error {
|
||||
tenant.Slug,
|
||||
tenant.Description,
|
||||
strings.Join(domains, ";"),
|
||||
visibility,
|
||||
orgUnitType,
|
||||
}
|
||||
}
|
||||
if err := writer.Write(row); err != nil {
|
||||
@@ -501,6 +508,8 @@ func parseTenantCSVRecords(r io.Reader) ([]tenantCSVRecord, error) {
|
||||
Slug: slug,
|
||||
Memo: tenantCSVValue(row, header, "memo"),
|
||||
Domains: splitTenantCSVDomains(tenantCSVValue(row, header, "email_domain")),
|
||||
Visibility: tenantCSVValue(row, header, "visibility"),
|
||||
OrgUnitType: tenantCSVValue(row, header, "org_unit_type"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -529,6 +538,16 @@ func tenantCSVHeaderIndex(header []string) map[string]int {
|
||||
"email_domain": "email_domain",
|
||||
"domain": "email_domain",
|
||||
"domains": "email_domain",
|
||||
"visibility": "visibility",
|
||||
"public_setting": "visibility",
|
||||
"publicsetting": "visibility",
|
||||
"orgunittype": "org_unit_type",
|
||||
"org_unit_type": "org_unit_type",
|
||||
"org-unit-type": "org_unit_type",
|
||||
"organizationtype": "org_unit_type",
|
||||
"organization_type": "org_unit_type",
|
||||
"orgtype": "org_unit_type",
|
||||
"org_type": "org_unit_type",
|
||||
}
|
||||
for i, column := range header {
|
||||
key := strings.ToLower(strings.TrimSpace(column))
|
||||
@@ -745,6 +764,45 @@ func tenantVisibility(config domain.JSONMap) string {
|
||||
}
|
||||
}
|
||||
|
||||
func tenantCSVOrgConfigValues(config domain.JSONMap) (string, string) {
|
||||
visibility := tenantVisibility(config)
|
||||
orgUnitType, _ := config["orgUnitType"].(string)
|
||||
return visibility, strings.TrimSpace(orgUnitType)
|
||||
}
|
||||
|
||||
func tenantCSVRecordConfig(record tenantCSVRecord) (domain.JSONMap, error) {
|
||||
config := map[string]any{}
|
||||
if strings.TrimSpace(record.Visibility) != "" {
|
||||
config["visibility"] = record.Visibility
|
||||
}
|
||||
if strings.TrimSpace(record.OrgUnitType) != "" {
|
||||
config["orgUnitType"] = record.OrgUnitType
|
||||
}
|
||||
if len(config) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return normalizeTenantConfig(config)
|
||||
}
|
||||
|
||||
func mergeTenantCSVRecordConfig(current domain.JSONMap, record tenantCSVRecord) (domain.JSONMap, bool, error) {
|
||||
recordConfig, err := tenantCSVRecordConfig(record)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(recordConfig) == 0 {
|
||||
return current, false, nil
|
||||
}
|
||||
|
||||
merged := make(domain.JSONMap, len(current)+len(recordConfig))
|
||||
for key, value := range current {
|
||||
merged[key] = value
|
||||
}
|
||||
for key, value := range recordConfig {
|
||||
merged[key] = value
|
||||
}
|
||||
return merged, true, nil
|
||||
}
|
||||
|
||||
func filterPublicTenants(tenants []domain.Tenant) []domain.Tenant {
|
||||
excludedIDs := make(map[string]bool)
|
||||
for _, tenant := range tenants {
|
||||
@@ -963,6 +1021,13 @@ func (h *TenantHandler) upsertTenantCSVRecord(c *fiber.Ctx, record tenantCSVReco
|
||||
if tenant.Status == "" {
|
||||
tenant.Status = domain.TenantStatusActive
|
||||
}
|
||||
mergedConfig, changedConfig, err := mergeTenantCSVRecordConfig(tenant.Config, record)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if changedConfig {
|
||||
tenant.Config = mergedConfig
|
||||
}
|
||||
|
||||
if err := h.DB.Save(&tenant).Error; err != nil {
|
||||
return nil, false, err
|
||||
@@ -999,6 +1064,13 @@ func (h *TenantHandler) createTenantCSVRecord(c *fiber.Ctx, record tenantCSVReco
|
||||
Description: record.Memo,
|
||||
Status: domain.TenantStatusActive,
|
||||
}
|
||||
config, _, err := mergeTenantCSVRecordConfig(nil, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(config) > 0 {
|
||||
tenant.Config = config
|
||||
}
|
||||
if err := h.DB.Create(&tenant).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1041,6 +1113,22 @@ func (h *TenantHandler) createTenantCSVRecord(c *fiber.Ctx, record tenantCSVReco
|
||||
}
|
||||
|
||||
tenant, err := h.Service.RegisterTenant(c.Context(), record.Name, record.Slug, record.Type, record.Memo, record.Domains, record.ParentTenantID, creatorID)
|
||||
if err != nil || tenant == nil {
|
||||
return tenant, err
|
||||
}
|
||||
config, changedConfig, err := mergeTenantCSVRecordConfig(tenant.Config, record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if changedConfig {
|
||||
if h.DB == nil {
|
||||
return nil, errors.New("database not available for tenant config import")
|
||||
}
|
||||
tenant.Config = config
|
||||
if err := h.DB.Save(tenant).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return tenant, err
|
||||
}
|
||||
|
||||
|
||||
@@ -454,6 +454,10 @@ func TestTenantHandler_ExportTenantsCSV(t *testing.T) {
|
||||
ParentID: &parentID,
|
||||
Slug: "tenant-a",
|
||||
Description: "Primary tenant",
|
||||
Config: domain.JSONMap{
|
||||
"visibility": "internal",
|
||||
"orgUnitType": "센터",
|
||||
},
|
||||
Domains: []domain.TenantDomain{
|
||||
{Domain: "tenant-a.example.com"},
|
||||
{Domain: "login.tenant-a.example.com"},
|
||||
@@ -470,8 +474,8 @@ func TestTenantHandler_ExportTenantsCSV(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Contains(t, resp.Header.Get("Content-Disposition"), "tenants.csv")
|
||||
assert.Equal(t, "text/csv", strings.Split(resp.Header.Get("Content-Type"), ";")[0])
|
||||
assert.Contains(t, string(body), "tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain")
|
||||
assert.Contains(t, string(body), "t1,Tenant A,COMPANY,parent-1,,tenant-a,Primary tenant,tenant-a.example.com;login.tenant-a.example.com")
|
||||
assert.Contains(t, string(body), "tenant_id,name,type,parent_tenant_id,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type")
|
||||
assert.Contains(t, string(body), "t1,Tenant A,COMPANY,parent-1,,tenant-a,Primary tenant,tenant-a.example.com;login.tenant-a.example.com,internal,센터")
|
||||
}
|
||||
|
||||
func TestTenantHandler_ExportTenantsCSV_OmitsIDsAndUsesParentSlug(t *testing.T) {
|
||||
@@ -506,7 +510,7 @@ func TestTenantHandler_ExportTenantsCSV_OmitsIDsAndUsesParentSlug(t *testing.T)
|
||||
text := string(body)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Contains(t, text, "name,type,parent_tenant_slug,slug,memo,email_domain")
|
||||
assert.Contains(t, text, "name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type")
|
||||
assert.Contains(t, text, "Child Tenant,USER_GROUP,parent-tenant,child-tenant,,")
|
||||
assert.NotContains(t, text, "tenant_id")
|
||||
assert.NotContains(t, text, "parent_tenant_id")
|
||||
@@ -663,13 +667,15 @@ func TestNormalizeTenantTypeAllowsOrganization(t *testing.T) {
|
||||
|
||||
func TestTenantCSVAllowedDomainsRoundTrip(t *testing.T) {
|
||||
records, err := parseTenantCSVRecords(strings.NewReader(
|
||||
"name,type,parent_tenant_slug,slug,memo,email_domain\n" +
|
||||
"Hanmac,COMPANY,,hanmac,,\"samaneng.com, hanmaceng.co.kr;login.hmac.kr\"\n",
|
||||
"name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type\n" +
|
||||
"Hanmac,COMPANY,,hanmac,,\"samaneng.com, hanmaceng.co.kr;login.hmac.kr\",internal,센터\n",
|
||||
))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, records, 1)
|
||||
assert.Equal(t, []string{"samaneng.com", "hanmaceng.co.kr", "login.hmac.kr"}, records[0].Domains)
|
||||
assert.Equal(t, "internal", records[0].Visibility)
|
||||
assert.Equal(t, "센터", records[0].OrgUnitType)
|
||||
}
|
||||
|
||||
func TestNormalizeTenantDomainInputsSplitsCommaAndWhitespace(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user