forked from baron/baron-sso
feat: implement multi-tenant member management and UI improvements
- Add multi-tenant support (isAddTenant, isRemoveTenant) to backend UpdateUser API. - Update UserRepository to support searching in company_codes array. - Implement table sorting and align search bar layout in adminfront. - Add 'Assign Existing Member' and 'Exclude from Organization' features to TenantUsersPage. - Auto-populate tenantSlug in UserCreatePage via query parameters. - Add necessary localization keys for new UI elements. Resolves #644, #639, #642, #641
This commit is contained in:
@@ -1432,6 +1432,8 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
Role *string `json:"role"`
|
||||
Status *string `json:"status"`
|
||||
CompanyCode *string `json:"companyCode"`
|
||||
IsAddTenant bool `json:"isAddTenant"`
|
||||
IsRemoveTenant bool `json:"isRemoveTenant"`
|
||||
Department *string `json:"department"`
|
||||
Position *string `json:"position"`
|
||||
JobTitle *string `json:"jobTitle"`
|
||||
@@ -1446,9 +1448,9 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
req.Metadata = mergeUserAppointmentMetadata(req.Metadata, req.AdditionalAppointments, req.PrimaryTenantID, req.PrimaryTenantName, req.PrimaryTenantIsOwner)
|
||||
|
||||
// [New] Tenant Admin restriction: Cannot change companyCode
|
||||
// [New] Tenant Admin restriction: Cannot change companyCode (except when adding/removing secondary membership)
|
||||
if requester != nil && domain.NormalizeRole(requester.Role) == domain.RoleTenantAdmin {
|
||||
if req.CompanyCode != nil && *req.CompanyCode != requester.CompanyCode {
|
||||
if !req.IsAddTenant && !req.IsRemoveTenant && req.CompanyCode != nil && *req.CompanyCode != requester.CompanyCode {
|
||||
return errorJSON(c, fiber.StatusForbidden, "forbidden: tenant admins cannot change user's tenant")
|
||||
}
|
||||
}
|
||||
@@ -1531,38 +1533,80 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
if req.CompanyCode != nil {
|
||||
code := strings.TrimSpace(*req.CompanyCode)
|
||||
traits["companyCode"] = code
|
||||
// Resolve TenantID for Kratos Trait
|
||||
if h.TenantService != nil && code != "" {
|
||||
if tenant, err := h.TenantService.GetTenantBySlug(c.Context(), code); err == nil && tenant != nil {
|
||||
traits["tenant_id"] = tenant.ID
|
||||
}
|
||||
}
|
||||
|
||||
// Add to existingCodes if not present
|
||||
found := false
|
||||
for _, existing := range existingCodes {
|
||||
if existing == code {
|
||||
found = true
|
||||
break
|
||||
if req.IsAddTenant {
|
||||
// Add to existingCodes if not present
|
||||
found := false
|
||||
for _, existing := range existingCodes {
|
||||
if existing == code {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found && code != "" {
|
||||
existingCodes = append(existingCodes, code)
|
||||
}
|
||||
} else if req.IsRemoveTenant {
|
||||
// Remove from existingCodes
|
||||
var newCodes []string
|
||||
for _, existing := range existingCodes {
|
||||
if existing != code {
|
||||
newCodes = append(newCodes, existing)
|
||||
}
|
||||
}
|
||||
existingCodes = newCodes
|
||||
|
||||
// If removing the primary company code, pick another one as primary if available
|
||||
currentPrimary := extractTraitString(traits, "companyCode")
|
||||
if currentPrimary == code {
|
||||
if len(existingCodes) > 0 {
|
||||
traits["companyCode"] = existingCodes[0]
|
||||
if h.TenantService != nil {
|
||||
if t, err := h.TenantService.GetTenantBySlug(c.Context(), existingCodes[0]); err == nil && t != nil {
|
||||
traits["tenant_id"] = t.ID
|
||||
}
|
||||
}
|
||||
} else {
|
||||
traits["companyCode"] = ""
|
||||
traits["tenant_id"] = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal update: replace primary company code and ensure it's in existingCodes
|
||||
traits["companyCode"] = code
|
||||
// Resolve TenantID for Kratos Trait
|
||||
if h.TenantService != nil && code != "" {
|
||||
if tenant, err := h.TenantService.GetTenantBySlug(c.Context(), code); err == nil && tenant != nil {
|
||||
traits["tenant_id"] = tenant.ID
|
||||
}
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, existing := range existingCodes {
|
||||
if existing == code {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found && code != "" {
|
||||
existingCodes = append(existingCodes, code)
|
||||
}
|
||||
}
|
||||
if !found && code != "" {
|
||||
existingCodes = append(existingCodes, code)
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and save back companyCodes
|
||||
var uniqueCodes []string
|
||||
var codesToSave []string
|
||||
seenCodes := map[string]bool{}
|
||||
for _, c := range existingCodes {
|
||||
if !seenCodes[c] && c != "" {
|
||||
seenCodes[c] = true
|
||||
uniqueCodes = append(uniqueCodes, c)
|
||||
codesToSave = append(codesToSave, c)
|
||||
}
|
||||
}
|
||||
if len(uniqueCodes) > 0 {
|
||||
traits["companyCodes"] = uniqueCodes
|
||||
if len(codesToSave) > 0 {
|
||||
traits["companyCodes"] = codesToSave
|
||||
} else {
|
||||
delete(traits, "companyCodes")
|
||||
}
|
||||
|
||||
if req.Department != nil {
|
||||
@@ -1927,6 +1971,17 @@ func (h *UserHandler) mapToLocalUser(identity service.KratosIdentity) *domain.Us
|
||||
UpdatedAt: identity.UpdatedAt,
|
||||
}
|
||||
|
||||
// [New] Sync multi-tenant codes
|
||||
if codes, ok := traits["companyCodes"].([]interface{}); ok {
|
||||
for _, v := range codes {
|
||||
if str, ok := v.(string); ok && str != "" {
|
||||
user.CompanyCodes = append(user.CompanyCodes, str)
|
||||
}
|
||||
}
|
||||
} else if codes, ok := traits["companyCodes"].([]string); ok {
|
||||
user.CompanyCodes = codes
|
||||
}
|
||||
|
||||
// 1. Try to get tenant_id directly from Kratos traits first (Fastest & most reliable)
|
||||
tID := extractTraitString(traits, "tenant_id")
|
||||
if tID != "" {
|
||||
|
||||
Reference in New Issue
Block a user