forked from baron/baron-sso
사용자 테넌트 소속 데이터 정리
This commit is contained in:
@@ -6,6 +6,25 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func CountOrphanUserTenantMemberships(ctx context.Context, db *gorm.DB) (int64, error) {
|
||||
var count int64
|
||||
err := db.WithContext(ctx).Raw(`
|
||||
SELECT COUNT(*)
|
||||
FROM users AS u
|
||||
WHERE u.deleted_at IS NULL
|
||||
AND (
|
||||
u.tenant_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tenants AS t
|
||||
WHERE t.id = u.tenant_id
|
||||
AND t.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
`).Scan(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func ClearOrphanUserTenantMemberships(ctx context.Context, db *gorm.DB) (int64, error) {
|
||||
result := db.WithContext(ctx).Exec(`
|
||||
WITH orphan_users AS (
|
||||
@@ -13,41 +32,17 @@ WITH orphan_users AS (
|
||||
FROM users AS u
|
||||
WHERE u.deleted_at IS NULL
|
||||
AND (
|
||||
(
|
||||
u.tenant_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tenants AS t
|
||||
WHERE t.id = u.tenant_id
|
||||
AND t.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
OR (
|
||||
NULLIF(BTRIM(u.company_code), '') IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tenants AS t
|
||||
WHERE LOWER(t.slug) = LOWER(BTRIM(u.company_code))
|
||||
AND t.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
OR EXISTS (
|
||||
u.tenant_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM UNNEST(COALESCE(u.company_codes, ARRAY[]::text[])) AS code(value)
|
||||
WHERE NULLIF(BTRIM(code.value), '') IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM tenants AS t
|
||||
WHERE LOWER(t.slug) = LOWER(BTRIM(code.value))
|
||||
AND t.deleted_at IS NULL
|
||||
)
|
||||
FROM tenants AS t
|
||||
WHERE t.id = u.tenant_id
|
||||
AND t.deleted_at IS NULL
|
||||
)
|
||||
)
|
||||
)
|
||||
UPDATE users AS u
|
||||
SET tenant_id = NULL,
|
||||
company_code = '',
|
||||
company_codes = NULL,
|
||||
updated_at = NOW()
|
||||
FROM orphan_users AS ou
|
||||
WHERE u.id = ou.id
|
||||
|
||||
@@ -44,6 +44,10 @@ func TestClearOrphanUserTenantMemberships(t *testing.T) {
|
||||
require.NoError(t, repo.Create(ctx, activeUser))
|
||||
require.NoError(t, repo.Create(ctx, orphanUser))
|
||||
|
||||
count, err := CountOrphanUserTenantMemberships(ctx, testDB)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), count)
|
||||
|
||||
affected, err := ClearOrphanUserTenantMemberships(ctx, testDB)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), affected)
|
||||
@@ -60,4 +64,8 @@ func TestClearOrphanUserTenantMemberships(t *testing.T) {
|
||||
assert.Nil(t, foundOrphan.TenantID)
|
||||
assert.Empty(t, foundOrphan.CompanyCode)
|
||||
assert.Empty(t, foundOrphan.CompanyCodes)
|
||||
|
||||
count, err = CountOrphanUserTenantMemberships(ctx, testDB)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
@@ -90,11 +90,6 @@ func (r *userProjectionRepository) CountTenantMembers(ctx context.Context, tenan
|
||||
FROM requested
|
||||
LEFT JOIN users ON users.deleted_at IS NULL AND (
|
||||
users.tenant_id::text = requested.tenant_id
|
||||
OR LOWER(users.company_code) = LOWER(requested.slug)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM unnest(users.company_codes) AS company_code
|
||||
WHERE LOWER(company_code) = LOWER(requested.slug)
|
||||
)
|
||||
)
|
||||
GROUP BY requested.tenant_id
|
||||
`, strings.Join(valuePlaceholders, ","))
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
@@ -152,33 +151,25 @@ func (r *userRepository) CountByCompanyCodes(ctx context.Context, codes []string
|
||||
}
|
||||
|
||||
type result struct {
|
||||
CompanyCode string
|
||||
Count int64
|
||||
TenantSlug string
|
||||
Count int64
|
||||
}
|
||||
var results []result
|
||||
|
||||
lowerCodes := lowerStrings(codes)
|
||||
|
||||
// Combine singular company_code and array company_codes using a subquery
|
||||
// to ensure we count each user accurately per company code they belong to.
|
||||
query := `
|
||||
SELECT LOWER(comp_code) as company_code, count(DISTINCT id) as count
|
||||
FROM (
|
||||
SELECT id, company_code as comp_code FROM users WHERE deleted_at IS NULL AND LOWER(company_code) = ANY($1)
|
||||
UNION ALL
|
||||
SELECT id, unnest(company_codes) as comp_code FROM users WHERE deleted_at IS NULL AND company_codes IS NOT NULL
|
||||
) as combined
|
||||
WHERE LOWER(comp_code) = ANY($1)
|
||||
GROUP BY LOWER(comp_code)
|
||||
`
|
||||
err := r.db.WithContext(ctx).Raw(query, pq.Array(lowerCodes)).Scan(&results).Error
|
||||
if err != nil {
|
||||
if err := r.db.WithContext(ctx).Table("users").
|
||||
Select("LOWER(tenants.slug) AS tenant_slug, count(DISTINCT users.id) AS count").
|
||||
Joins("JOIN tenants ON users.tenant_id = tenants.id").
|
||||
Where("users.deleted_at IS NULL AND LOWER(tenants.slug) IN ?", lowerCodes).
|
||||
Group("LOWER(tenants.slug)").
|
||||
Scan(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
counts := make(map[string]int64)
|
||||
for _, res := range results {
|
||||
counts[strings.ToLower(res.CompanyCode)] = res.Count
|
||||
counts[strings.ToLower(res.TenantSlug)] = res.Count
|
||||
}
|
||||
|
||||
// Ensure all requested codes are present in results (even if count is 0)
|
||||
@@ -207,13 +198,13 @@ func (r *userRepository) List(ctx context.Context, offset, limit int, search str
|
||||
|
||||
if tenantSlug != "" {
|
||||
db = db.Joins("LEFT JOIN tenants ON users.tenant_id = tenants.id").
|
||||
Where("users.company_code = ? OR ? = ANY(users.company_codes) OR tenants.slug = ?", tenantSlug, tenantSlug, tenantSlug)
|
||||
Where("tenants.slug = ?", tenantSlug)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
searchTerm := "%" + search + "%"
|
||||
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)
|
||||
db = db.Where("(users.email LIKE ? OR users.name LIKE ? OR users.metadata::text LIKE ?)",
|
||||
searchTerm, searchTerm, searchTerm)
|
||||
}
|
||||
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
@@ -281,6 +272,10 @@ func (r *userRepository) FindByTenantIDs(ctx context.Context, tenantIDs []string
|
||||
|
||||
func (r *userRepository) FindByCompanyCodes(ctx context.Context, codes []string) ([]domain.User, error) {
|
||||
var users []domain.User
|
||||
err := r.db.WithContext(ctx).Where("company_code IN ?", codes).Find(&users).Error
|
||||
err := r.db.WithContext(ctx).
|
||||
Joins("JOIN tenants ON users.tenant_id = tenants.id").
|
||||
Where("LOWER(tenants.slug) IN ?", lowerStrings(codes)).
|
||||
Preload("Tenant").
|
||||
Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user