1
0
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:
2026-05-06 14:20:35 +09:00
parent 3169dd958a
commit 5f9a61de98
10 changed files with 591 additions and 140 deletions

View File

@@ -5,6 +5,7 @@ import (
"context"
"strings"
"github.com/lib/pq"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -156,11 +157,12 @@ func (r *userRepository) CountByCompanyCodes(ctx context.Context, codes []string
}
var results []result
// Search by company_code directly. Normalize inputs using LOWER for robust matching.
err := r.db.WithContext(ctx).Model(&domain.User{}).
Select("LOWER(company_code) as company_code, count(*) as count").
Where("LOWER(company_code) IN ?", lowerStrings(codes)).
Group("LOWER(company_code)").
// Search by company_codes array using unnest and overlap.
// This ensures users with multiple memberships are counted in each tenant they belong to.
err := r.db.WithContext(ctx).Table("users").
Select("unnest(company_codes) as company_code, count(*) as count").
Where("company_codes && ?", pq.Array(lowerStrings(codes))).
Group("company_code").
Scan(&results).Error
if err != nil {
return nil, err
@@ -168,7 +170,7 @@ func (r *userRepository) CountByCompanyCodes(ctx context.Context, codes []string
counts := make(map[string]int64)
for _, res := range results {
counts[res.CompanyCode] = res.Count
counts[strings.ToLower(res.CompanyCode)] = res.Count
}
// Ensure all requested codes are present in results (even if count is 0)
@@ -196,15 +198,15 @@ func (r *userRepository) List(ctx context.Context, offset, limit int, search str
db := r.db.WithContext(ctx).Model(&domain.User{})
if companyCode != "" {
// [Matrix Fix] Match users either by their primary company code OR by the slug of the department they are attached to
// [Matrix Fix] Match users either by their primary company code OR by being in the company_codes array OR by tenant slug
db = db.Joins("LEFT JOIN tenants ON users.tenant_id = tenants.id").
Where("users.company_code = ? OR tenants.slug = ?", companyCode, companyCode)
Where("users.company_code = ? OR ? = ANY(users.company_codes) OR tenants.slug = ?", companyCode, companyCode, companyCode)
}
if search != "" {
searchTerm := "%" + search + "%"
db = db.Where("(users.email LIKE ? OR users.name LIKE ? OR users.company_code LIKE ? OR users.metadata::text LIKE ?)",
searchTerm, searchTerm, searchTerm, searchTerm)
db = db.Where("(users.email LIKE ? OR users.name LIKE ? OR users.company_code LIKE ? OR ? = ANY(users.company_codes) OR users.metadata::text LIKE ?)",
searchTerm, searchTerm, searchTerm, search, searchTerm)
}
if err := db.Count(&total).Error; err != nil {