forked from baron/baron-sso
adminfront 조직 통계오류 보정. Kratos Projection용 통계테이블 구조 추가
This commit is contained in:
163
backend/internal/service/user_projection_sync_service.go
Normal file
163
backend/internal/service/user_projection_sync_service.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type UserProjectionSyncService struct {
|
||||
kratos KratosAdminService
|
||||
repo repository.UserProjectionRepository
|
||||
}
|
||||
|
||||
type UserProjectionReconciler interface {
|
||||
Reconcile(ctx context.Context) (int, error)
|
||||
}
|
||||
|
||||
func NewUserProjectionSyncService(kratos KratosAdminService, repo repository.UserProjectionRepository) *UserProjectionSyncService {
|
||||
return &UserProjectionSyncService{
|
||||
kratos: kratos,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserProjectionSyncService) Reconcile(ctx context.Context) (int, error) {
|
||||
if s == nil || s.kratos == nil || s.repo == nil {
|
||||
return 0, fmt.Errorf("user projection sync dependencies are not configured")
|
||||
}
|
||||
|
||||
identities, err := s.kratos.ListIdentities(ctx)
|
||||
if err != nil {
|
||||
_ = s.repo.MarkFailed(ctx, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
users := make([]domain.User, 0, len(identities))
|
||||
for _, identity := range identities {
|
||||
users = append(users, MapKratosIdentityToLocalUser(identity))
|
||||
}
|
||||
if err := s.repo.ReplaceAllFromKratos(ctx, users); err != nil {
|
||||
_ = s.repo.MarkFailed(ctx, err)
|
||||
return 0, err
|
||||
}
|
||||
return len(users), nil
|
||||
}
|
||||
|
||||
func MapKratosIdentityToLocalUser(identity KratosIdentity) domain.User {
|
||||
traits := identity.Traits
|
||||
now := time.Now()
|
||||
createdAt := identity.CreatedAt
|
||||
if createdAt.IsZero() {
|
||||
createdAt = now
|
||||
}
|
||||
updatedAt := identity.UpdatedAt
|
||||
if updatedAt.IsZero() {
|
||||
updatedAt = now
|
||||
}
|
||||
|
||||
role := kratosProjectionTraitString(traits, "grade")
|
||||
if role == "" {
|
||||
role = kratosProjectionTraitString(traits, "role")
|
||||
}
|
||||
role = domain.NormalizeRole(role)
|
||||
if role == "" {
|
||||
role = domain.RoleUser
|
||||
}
|
||||
|
||||
companyCode := kratosProjectionTraitString(traits, "companyCode")
|
||||
if companyCode == "" {
|
||||
companyCode = kratosProjectionTraitString(traits, "company_code")
|
||||
}
|
||||
|
||||
user := domain.User{
|
||||
ID: identity.ID,
|
||||
Email: kratosProjectionTraitString(traits, "email"),
|
||||
Name: kratosProjectionTraitString(traits, "name"),
|
||||
Phone: kratosProjectionTraitString(traits, "phone_number"),
|
||||
Role: role,
|
||||
Status: normalizeProjectionStatus(identity.State),
|
||||
CompanyCode: companyCode,
|
||||
CompanyCodes: pq.StringArray(kratosProjectionTraitStringArray(traits, "companyCodes")),
|
||||
Department: kratosProjectionTraitString(traits, "department"),
|
||||
Position: kratosProjectionTraitString(traits, "position"),
|
||||
JobTitle: kratosProjectionTraitString(traits, "jobTitle"),
|
||||
AffiliationType: kratosProjectionTraitString(traits, "affiliationType"),
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
Metadata: make(domain.JSONMap),
|
||||
}
|
||||
if tenantID := kratosProjectionTraitString(traits, "tenant_id"); tenantID != "" {
|
||||
user.TenantID = &tenantID
|
||||
}
|
||||
if relyingPartyID := kratosProjectionTraitString(traits, "relying_party_id"); relyingPartyID != "" {
|
||||
user.RelyingPartyID = &relyingPartyID
|
||||
}
|
||||
|
||||
coreTraits := map[string]bool{
|
||||
"email": true, "name": true, "phone_number": true,
|
||||
"grade": true, "role": true,
|
||||
"companyCode": true, "company_code": true, "companyCodes": true,
|
||||
"tenant_id": true, "department": true,
|
||||
"position": true, "jobTitle": true, "affiliationType": true,
|
||||
"relying_party_id": true, "custom_login_ids": true, "id": true,
|
||||
}
|
||||
for key, value := range traits {
|
||||
if !coreTraits[key] {
|
||||
user.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func kratosProjectionTraitString(traits map[string]interface{}, key string) string {
|
||||
if traits == nil {
|
||||
return ""
|
||||
}
|
||||
value, ok := traits[key]
|
||||
if !ok || value == nil {
|
||||
return ""
|
||||
}
|
||||
if str, ok := value.(string); ok {
|
||||
return str
|
||||
}
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
|
||||
func kratosProjectionTraitStringArray(traits map[string]interface{}, key string) []string {
|
||||
if traits == nil {
|
||||
return nil
|
||||
}
|
||||
switch value := traits[key].(type) {
|
||||
case []string:
|
||||
return value
|
||||
case []interface{}:
|
||||
items := make([]string, 0, len(value))
|
||||
for _, item := range value {
|
||||
if str, ok := item.(string); ok && strings.TrimSpace(str) != "" {
|
||||
items = append(items, str)
|
||||
}
|
||||
}
|
||||
return items
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeProjectionStatus(state string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(state)) {
|
||||
case "blocked", domain.UserStatusInactive:
|
||||
return domain.UserStatusInactive
|
||||
case domain.UserStatusSuspended:
|
||||
return domain.UserStatusSuspended
|
||||
case domain.UserStatusLeaveOfAbsence:
|
||||
return domain.UserStatusLeaveOfAbsence
|
||||
default:
|
||||
return domain.UserStatusActive
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user