forked from baron/baron-sso
정합성 위반사항 확인 및 조치기능 추가
This commit is contained in:
@@ -3,6 +3,8 @@ package repository
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -10,6 +12,8 @@ import (
|
||||
|
||||
type DataIntegrityChecker interface {
|
||||
CheckDataIntegrity(ctx context.Context) (domain.DataIntegrityReport, error)
|
||||
ListOrphanUserLoginIDs(ctx context.Context) ([]domain.OrphanUserLoginID, error)
|
||||
DeleteOrphanUserLoginIDs(ctx context.Context, ids []string) (domain.DeleteOrphanUserLoginIDsResult, error)
|
||||
}
|
||||
|
||||
type dataIntegrityChecker struct {
|
||||
@@ -24,6 +28,14 @@ func (c *dataIntegrityChecker) CheckDataIntegrity(ctx context.Context) (domain.D
|
||||
return CheckDataIntegrity(ctx, c.db)
|
||||
}
|
||||
|
||||
func (c *dataIntegrityChecker) ListOrphanUserLoginIDs(ctx context.Context) ([]domain.OrphanUserLoginID, error) {
|
||||
return ListOrphanUserLoginIDs(ctx, c.db, nil)
|
||||
}
|
||||
|
||||
func (c *dataIntegrityChecker) DeleteOrphanUserLoginIDs(ctx context.Context, ids []string) (domain.DeleteOrphanUserLoginIDsResult, error) {
|
||||
return DeleteOrphanUserLoginIDs(ctx, c.db, ids)
|
||||
}
|
||||
|
||||
func CheckDataIntegrity(ctx context.Context, db *gorm.DB) (domain.DataIntegrityReport, error) {
|
||||
tenantChecks := []domain.DataIntegrityCheck{
|
||||
{
|
||||
@@ -224,3 +236,145 @@ func summarizeSectionStatus(sections []domain.DataIntegritySection) domain.DataI
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func ListOrphanUserLoginIDs(ctx context.Context, db *gorm.DB, ids []string) ([]domain.OrphanUserLoginID, error) {
|
||||
type orphanRow struct {
|
||||
ID string
|
||||
UserID string
|
||||
UserEmail string
|
||||
UserDeletedAt *time.Time
|
||||
TenantID string
|
||||
TenantSlug string
|
||||
TenantDeletedAt *time.Time
|
||||
FieldKey string
|
||||
LoginID string
|
||||
MissingUser bool
|
||||
DeletedUser bool
|
||||
MissingTenant bool
|
||||
DeletedTenant bool
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
uli.id,
|
||||
uli.user_id,
|
||||
COALESCE(u.email, '') AS user_email,
|
||||
u.deleted_at AS user_deleted_at,
|
||||
uli.tenant_id,
|
||||
COALESCE(t.slug, '') AS tenant_slug,
|
||||
t.deleted_at AS tenant_deleted_at,
|
||||
uli.field_key,
|
||||
uli.login_id,
|
||||
(u.id IS NULL) AS missing_user,
|
||||
(u.id IS NOT NULL AND u.deleted_at IS NOT NULL) AS deleted_user,
|
||||
(t.id IS NULL) AS missing_tenant,
|
||||
(t.id IS NOT NULL AND t.deleted_at IS NOT NULL) AS deleted_tenant
|
||||
FROM user_login_ids AS uli
|
||||
LEFT JOIN users AS u ON u.id = uli.user_id
|
||||
LEFT JOIN tenants AS t ON t.id = uli.tenant_id
|
||||
WHERE (
|
||||
u.id IS NULL
|
||||
OR u.deleted_at IS NOT NULL
|
||||
OR t.id IS NULL
|
||||
OR t.deleted_at IS NOT NULL
|
||||
)
|
||||
`
|
||||
args := []any{}
|
||||
if len(ids) > 0 {
|
||||
query += " AND uli.id IN ?\n"
|
||||
args = append(args, ids)
|
||||
}
|
||||
query += "ORDER BY uli.login_id, uli.id"
|
||||
|
||||
var rows []orphanRow
|
||||
if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]domain.OrphanUserLoginID, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
reasons := make([]string, 0, 4)
|
||||
if row.MissingUser {
|
||||
reasons = append(reasons, "missing_user")
|
||||
}
|
||||
if row.DeletedUser {
|
||||
reasons = append(reasons, "deleted_user")
|
||||
}
|
||||
if row.MissingTenant {
|
||||
reasons = append(reasons, "missing_tenant")
|
||||
}
|
||||
if row.DeletedTenant {
|
||||
reasons = append(reasons, "deleted_tenant")
|
||||
}
|
||||
items = append(items, domain.OrphanUserLoginID{
|
||||
ID: row.ID,
|
||||
UserID: row.UserID,
|
||||
UserEmail: row.UserEmail,
|
||||
UserDeletedAt: row.UserDeletedAt,
|
||||
TenantID: row.TenantID,
|
||||
TenantSlug: row.TenantSlug,
|
||||
TenantDeletedAt: row.TenantDeletedAt,
|
||||
FieldKey: row.FieldKey,
|
||||
LoginID: row.LoginID,
|
||||
Reasons: reasons,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func DeleteOrphanUserLoginIDs(ctx context.Context, db *gorm.DB, ids []string) (domain.DeleteOrphanUserLoginIDsResult, error) {
|
||||
ids = normalizeIDList(ids)
|
||||
result := domain.DeleteOrphanUserLoginIDsResult{
|
||||
Deleted: []domain.OrphanUserLoginID{},
|
||||
SkippedIDs: []string{},
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
items, err := ListOrphanUserLoginIDs(ctx, tx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletableIDs := make([]string, 0, len(items))
|
||||
deletableSet := make(map[string]bool, len(items))
|
||||
for _, item := range items {
|
||||
deletableIDs = append(deletableIDs, item.ID)
|
||||
deletableSet[item.ID] = true
|
||||
}
|
||||
for _, id := range ids {
|
||||
if !deletableSet[id] {
|
||||
result.SkippedIDs = append(result.SkippedIDs, id)
|
||||
}
|
||||
}
|
||||
if len(deletableIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleteResult := tx.Exec("DELETE FROM user_login_ids WHERE id IN ?", deletableIDs)
|
||||
if deleteResult.Error != nil {
|
||||
return deleteResult.Error
|
||||
}
|
||||
result.Deleted = items
|
||||
result.DeletedCount = deleteResult.RowsAffected
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func normalizeIDList(ids []string) []string {
|
||||
normalized := make([]string, 0, len(ids))
|
||||
seen := map[string]bool{}
|
||||
for _, id := range ids {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" || seen[id] {
|
||||
continue
|
||||
}
|
||||
seen[id] = true
|
||||
normalized = append(normalized, id)
|
||||
}
|
||||
slices.Sort(normalized)
|
||||
return normalized
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user