1
0
forked from baron/baron-sso

사용자 테넌트 소속 데이터 정리

This commit is contained in:
2026-05-13 18:23:39 +09:00
parent 8a6e41d74c
commit e36a973053
26 changed files with 348 additions and 387 deletions

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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, ","))

View File

@@ -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
}