forked from baron/baron-sso
adminfront 조직 통계오류 보정. Kratos Projection용 통계테이블 구조 추가
This commit is contained in:
@@ -6,8 +6,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type UserGroupService interface {
|
||||
@@ -210,40 +212,56 @@ func (s *userGroupService) AddMember(ctx context.Context, groupID, userID string
|
||||
return fmt.Errorf("user group not found: %w", err)
|
||||
}
|
||||
|
||||
// [Fix] Sync Kratos Traits & Local DB when a user is added to an organization
|
||||
if s.kratos != nil && s.tenantRepo != nil {
|
||||
tenant, err := s.tenantRepo.FindByID(ctx, group.TenantID)
|
||||
if err == nil && tenant != nil {
|
||||
// Fetch Kratos Identity
|
||||
identity, err := s.kratos.GetIdentity(ctx, userID)
|
||||
if err == nil && identity != nil {
|
||||
traits := identity.Traits
|
||||
if traits == nil {
|
||||
traits = make(map[string]interface{})
|
||||
}
|
||||
traits["companyCode"] = tenant.Slug
|
||||
traits["tenant_id"] = tenant.ID
|
||||
traits["department"] = group.Name
|
||||
var tenant *domain.Tenant
|
||||
if s.tenantRepo != nil {
|
||||
tenant, _ = s.tenantRepo.FindByID(ctx, group.TenantID)
|
||||
}
|
||||
|
||||
// Update Kratos
|
||||
_, updateErr := s.kratos.UpdateIdentity(ctx, userID, traits, identity.State)
|
||||
if updateErr != nil {
|
||||
slog.Error("Failed to update identity traits during AddMember", "user", userID, "error", updateErr)
|
||||
}
|
||||
var updatedIdentity *KratosIdentity
|
||||
|
||||
// [Fix] Sync Kratos Traits & Local DB when a user is added to an organization
|
||||
if s.kratos != nil && tenant != nil {
|
||||
// Fetch Kratos Identity
|
||||
identity, err := s.kratos.GetIdentity(ctx, userID)
|
||||
if err == nil && identity != nil {
|
||||
traits := identity.Traits
|
||||
if traits == nil {
|
||||
traits = make(map[string]interface{})
|
||||
}
|
||||
traits["companyCode"] = tenant.Slug
|
||||
traits["tenant_id"] = tenant.ID
|
||||
traits["department"] = group.Name
|
||||
|
||||
// Update Kratos
|
||||
updated, updateErr := s.kratos.UpdateIdentity(ctx, userID, traits, identity.State)
|
||||
if updateErr != nil {
|
||||
slog.Error("Failed to update identity traits during AddMember", "user", userID, "error", updateErr)
|
||||
} else if updated != nil {
|
||||
updatedIdentity = updated
|
||||
} else {
|
||||
identity.Traits = traits
|
||||
updatedIdentity = identity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync local user repo
|
||||
if s.userRepo != nil && s.tenantRepo != nil {
|
||||
tenant, _ := s.tenantRepo.FindByID(ctx, group.TenantID)
|
||||
if tenant != nil {
|
||||
localUser, err := s.userRepo.FindByID(ctx, userID)
|
||||
if err == nil && localUser != nil {
|
||||
localUser.CompanyCode = tenant.Slug
|
||||
localUser.TenantID = &tenant.ID
|
||||
localUser.Department = group.Name
|
||||
_ = s.userRepo.Update(ctx, localUser)
|
||||
if s.userRepo != nil && tenant != nil {
|
||||
localUser, err := s.userRepo.FindByID(ctx, userID)
|
||||
if err != nil || localUser == nil {
|
||||
if updatedIdentity != nil {
|
||||
localUser = mapUserGroupKratosIdentityToLocalUser(*updatedIdentity)
|
||||
} else {
|
||||
slog.Warn("Skipping local user sync during AddMember because identity projection is unavailable", "user", userID, "error", err)
|
||||
localUser = nil
|
||||
}
|
||||
}
|
||||
if localUser != nil {
|
||||
localUser.CompanyCode = tenant.Slug
|
||||
localUser.TenantID = &tenant.ID
|
||||
localUser.Department = group.Name
|
||||
if err := s.userRepo.Update(ctx, localUser); err != nil {
|
||||
slog.Error("Failed to sync local user during AddMember", "user", userID, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,6 +289,116 @@ func (s *userGroupService) AddMember(ctx context.Context, groupID, userID string
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapUserGroupKratosIdentityToLocalUser(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 := userGroupTraitString(traits, "grade")
|
||||
if role == "" {
|
||||
role = userGroupTraitString(traits, "role")
|
||||
}
|
||||
role = domain.NormalizeRole(role)
|
||||
if role == "" {
|
||||
role = domain.RoleUser
|
||||
}
|
||||
|
||||
companyCode := userGroupTraitString(traits, "companyCode")
|
||||
if companyCode == "" {
|
||||
companyCode = userGroupTraitString(traits, "company_code")
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
ID: identity.ID,
|
||||
Email: userGroupTraitString(traits, "email"),
|
||||
Name: userGroupTraitString(traits, "name"),
|
||||
Phone: userGroupTraitString(traits, "phone_number"),
|
||||
Role: role,
|
||||
Status: userGroupIdentityStatus(identity.State),
|
||||
CompanyCode: companyCode,
|
||||
Department: userGroupTraitString(traits, "department"),
|
||||
Position: userGroupTraitString(traits, "position"),
|
||||
JobTitle: userGroupTraitString(traits, "jobTitle"),
|
||||
AffiliationType: userGroupTraitString(traits, "affiliationType"),
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
Metadata: make(domain.JSONMap),
|
||||
}
|
||||
if tenantID := userGroupTraitString(traits, "tenant_id"); tenantID != "" {
|
||||
user.TenantID = &tenantID
|
||||
}
|
||||
if relyingPartyID := userGroupTraitString(traits, "relying_party_id"); relyingPartyID != "" {
|
||||
user.RelyingPartyID = &relyingPartyID
|
||||
}
|
||||
user.CompanyCodes = pq.StringArray(userGroupTraitStringArray(traits, "companyCodes"))
|
||||
|
||||
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 userGroupTraitString(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 userGroupTraitStringArray(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 && str != "" {
|
||||
items = append(items, str)
|
||||
}
|
||||
}
|
||||
return items
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func userGroupIdentityStatus(state string) string {
|
||||
switch state {
|
||||
case "", "active":
|
||||
return domain.UserStatusActive
|
||||
case "inactive":
|
||||
return domain.UserStatusInactive
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
func (s *userGroupService) RemoveMember(ctx context.Context, groupID, userID string) error {
|
||||
// Validate group exists
|
||||
if _, err := s.repo.FindByID(ctx, groupID); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user