diff --git a/adminfront/src/locales/template.toml b/adminfront/src/locales/template.toml index 3ef4a953..83582b7d 100644 --- a/adminfront/src/locales/template.toml +++ b/adminfront/src/locales/template.toml @@ -182,9 +182,18 @@ description = "" [msg.admin.integrity.check.orphan_user_tenant_memberships] description = "" -[msg.admin.integrity] +[ui.admin.integrity] +tab_checks = "" +tab_user_projection = "" subtitle = "" +[ui.admin.tenants.profile] +worksmobile_enabled = "" +worksmobile_excluded = "" +worksmobile_sync = "" +allowed_domains = "" + + [msg.admin.user_projection] action_error = "" action_success = "" diff --git a/backend/internal/handler/dev_handler_isolation_test.go b/backend/internal/handler/dev_handler_isolation_test.go index 73e127f1..094dd2ab 100644 --- a/backend/internal/handler/dev_handler_isolation_test.go +++ b/backend/internal/handler/dev_handler_isolation_test.go @@ -124,7 +124,7 @@ func TestDevHandler_Isolation(t *testing.T) { mockKeto.On("CheckPermission", mock.Anything, "User:user-a", "RelyingParty", "client-tenant-a", "view").Return(true, nil).Maybe() // Deny for other clients mockKeto.On("CheckPermission", mock.Anything, "User:user-a", "RelyingParty", "client-tenant-b", "view").Return(false, nil).Maybe() - + mockKeto.On("ListRelations", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]service.RelationTuple{}, nil).Maybe() req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients", nil) @@ -155,7 +155,7 @@ func TestDevHandler_Isolation(t *testing.T) { return c.Next() }) app.Get("/api/v1/dev/clients", h.ListClients) - + // Deny all by default mockKeto.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(false, nil).Maybe() diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index 92a75112..64157614 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -1020,6 +1020,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error { ID string Slug string Name string + ParentID *string Schema []any Groups []domain.UserGroup LoginIDField string @@ -1030,9 +1031,10 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error { buildTenantCacheItem := func(tenant *domain.Tenant) tenantCacheItem { tItem := tenantCacheItem{ - ID: tenant.ID, - Slug: tenant.Slug, - Name: tenant.Name, + ID: tenant.ID, + Slug: tenant.Slug, + Name: tenant.Name, + ParentID: tenant.ParentID, } if s, ok := tenant.Config["userSchema"].([]any); ok { tItem.Schema = s diff --git a/backend/internal/handler/user_handler_test.go b/backend/internal/handler/user_handler_test.go index 666578f2..94923205 100644 --- a/backend/internal/handler/user_handler_test.go +++ b/backend/internal/handler/user_handler_test.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "net/http/httptest" + "slices" "strings" "testing" "time" @@ -1105,11 +1106,17 @@ func TestUserHandler_BulkCreateUsers_HanmacEmailPolicy(t *testing.T) { mockTenant.On("GetTenantBySlug", mock.Anything, "h-company").Return(&hTenants[1], nil).Maybe() mockTenant.On("GetTenant", mock.Anything, hCompanyID).Return(&hTenants[1], nil).Maybe() mockTenant.On("ListTenants", mock.Anything, 10000, 0, "").Return(hTenants, int64(len(hTenants)), nil).Maybe() - - mockRepo.On("FindByTenantIDs", mock.Anything, mock.Anything).Return([]domain.User{ - {Email: "han@hanmaceng.co.kr", TenantID: &hCompanyID}, + + mockRepo.On("FindByTenantIDs", mock.Anything, mock.MatchedBy(func(ids []string) bool { + return slices.Contains(ids, hRootID) || slices.Contains(ids, hCompanyID) + })).Return([]domain.User{ + {Email: "han@hanmaceng.co.kr", TenantID: &hCompanyID, CompanyCode: "h-company"}, + }, nil).Maybe() + mockRepo.On("FindByCompanyCodes", mock.Anything, mock.MatchedBy(func(codes []string) bool { + return slices.Contains(codes, "h-company") || slices.Contains(codes, "hanmac-family") + })).Return([]domain.User{ + {Email: "han@hanmaceng.co.kr", TenantID: &hCompanyID, CompanyCode: "h-company"}, }, nil).Maybe() - mockRepo.On("FindByCompanyCodes", mock.Anything, mock.Anything).Return([]domain.User{}, nil).Maybe() mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil).Maybe() payload := map[string]any{ diff --git a/locales/en.toml b/locales/en.toml index 48d6f898..dde44f75 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -1368,6 +1368,9 @@ subtitle = "Slug and status changes are applied immediately." title = "Tenant Profile" type = "Type" visibility = "Visibility" +worksmobile_enabled = "Worksmobile Enabled" +worksmobile_excluded = "Excluded from Worksmobile" +worksmobile_sync = "Worksmobile Sync Status" [ui.admin.tenants.profile.form] parent = "Parent Tenant (Optional)" @@ -2760,6 +2763,8 @@ subtitle = "Review and sync the Kratos user read model." description = "This screen is only available to super_admin users." [ui.admin.integrity] +tab_checks = "Integrity Checks" +tab_user_projection = "User Projection" fetch_error = "Unable to load the final integrity check result." kicker = "System" loading = "Loading data integrity report..." diff --git a/locales/ko.toml b/locales/ko.toml index cf67c4ea..e99be6df 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -1831,6 +1831,9 @@ subtitle = "슬러그 및 상태 변경은 즉시 적용됩니다." title = "테넌트 프로필" type = "테넌트 유형" visibility = "공개 범위" +worksmobile_enabled = "웍스모바일 활성화" +worksmobile_excluded = "웍스모바일 제외" +worksmobile_sync = "웍스모바일 동기화 상태" [ui.admin.tenants.profile.form] parent = "상위 테넌트 (선택)" @@ -3183,10 +3186,16 @@ subtitle = "Kratos 사용자 read model을 확인하고 동기화 상태를 갱 [msg.admin.user_projection.forbidden] description = "이 화면은 super_admin 권한으로만 접근할 수 있습니다." +[msg.admin.integrity] +subtitle = "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다." + [ui.admin.integrity] +tab_checks = "정합성 검사" +tab_user_projection = "사용자 동기화" fetch_error = "정합성 최종 검증 결과를 불러오지 못했습니다." kicker = "시스템" loading = "불러오는 중" +subtitle = "정합성 상태를 확인하고 데이터 모델 전반의 검증 결과를 살펴봅니다." title = "데이터 정합성 검증" [ui.admin.integrity.forbidden] diff --git a/locales/template.toml b/locales/template.toml index 08639c33..bd9cf3f9 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -3045,12 +3045,39 @@ description = "" [msg.admin.integrity.check.orphan_user_tenant_memberships] description = "" +[ui.admin.integrity] +tab_checks = "" +tab_user_projection = "" +subtitle = "" + +[ui.admin.tenants.profile] +worksmobile_enabled = "" +worksmobile_excluded = "" +worksmobile_sync = "" +allowed_domains = "" + + [msg.admin.integrity] subtitle = "" [msg.admin.integrity.section.tenant_integrity] description = "" +[ui.admin.integrity] +fetch_error = "" +kicker = "" +loading = "" +subtitle = "" +tab_checks = "" +tab_user_projection = "" +title = "" + +[ui.admin.tenants.profile] +allowed_domains = "" +worksmobile_enabled = "" +worksmobile_excluded = "" +worksmobile_sync = "" + [msg.admin.integrity.section.user_integrity] description = ""