1
0
forked from baron/baron-sso

adminfront 조직 통계오류 보정. Kratos Projection용 통계테이블 구조 추가

This commit is contained in:
2026-05-11 13:01:55 +09:00
parent 9a64a16cb9
commit 843b4100ad
36 changed files with 2022 additions and 169 deletions

View File

@@ -100,6 +100,7 @@ type AuthHandler struct {
KetoService service.KetoService
KetoOutboxRepo repository.KetoOutboxRepository
UserRepo repository.UserRepository
UserProjectionRepo repository.UserProjectionRepository
ConsentRepo repository.ClientConsentRepository
RPUserMetadataRepo repository.RPUserMetadataRepository
RPUsageSink domain.RPUsageEventSink
@@ -856,6 +857,7 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error {
if err := h.UserRepo.Update(ctx, u); err != nil {
slog.Error("[Signup] Failed to sync user to Read-Model (Local DB)", "email", u.Email, "error", err)
markUserProjectionFailed(ctx, h.UserProjectionRepo, err)
} else {
slog.Debug("[Signup] Synced user to Read-Model", "email", u.Email)
@@ -865,6 +867,7 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error {
}
if err := h.UserRepo.UpdateUserLoginIDs(ctx, u.ID, ids); err != nil {
slog.Error("[Signup] Failed to update user login IDs", "userID", u.ID, "error", err)
markUserProjectionFailed(ctx, h.UserProjectionRepo, err)
}
// [Keto] Sync user-tenant relationship via Outbox
@@ -7242,6 +7245,115 @@ func (h *AuthHandler) mapKratosIdentityToProfile(identityID string, traits map[s
return profile
}
func (h *AuthHandler) mapKratosTraitsToLocalUser(identityID string, traits map[string]interface{}, existing *domain.User) *domain.User {
now := time.Now()
localUser := &domain.User{
ID: identityID,
Status: domain.UserStatusActive,
CreatedAt: now,
UpdatedAt: now,
Metadata: make(domain.JSONMap),
}
if existing != nil {
copied := *existing
localUser = &copied
localUser.UpdatedAt = now
if localUser.Metadata == nil {
localUser.Metadata = make(domain.JSONMap)
}
}
if email := extractTraitString(traits, "email"); email != "" {
localUser.Email = email
}
if name := extractTraitString(traits, "name"); name != "" {
localUser.Name = name
}
if phone := extractTraitString(traits, "phone_number"); phone != "" {
localUser.Phone = phone
}
if department := extractTraitString(traits, "department"); department != "" {
localUser.Department = department
}
if position := extractTraitString(traits, "position"); position != "" {
localUser.Position = position
}
if jobTitle := extractTraitString(traits, "jobTitle"); jobTitle != "" {
localUser.JobTitle = jobTitle
}
if affType := extractTraitString(traits, "affiliationType"); affType != "" {
localUser.AffiliationType = affType
}
companyCode := extractTraitString(traits, "companyCode")
if companyCode == "" {
companyCode = extractTraitString(traits, "company_code")
}
if companyCode != "" {
localUser.CompanyCode = companyCode
}
if companyCodes := extractTraitStringArray(traits, "companyCodes"); len(companyCodes) > 0 {
localUser.CompanyCodes = pq.StringArray(companyCodes)
}
if tenantID := extractTraitString(traits, "tenant_id"); tenantID != "" {
localUser.TenantID = &tenantID
}
if relyingPartyID := extractTraitString(traits, "relying_party_id"); relyingPartyID != "" {
localUser.RelyingPartyID = &relyingPartyID
}
role := extractTraitString(traits, "grade")
if role == "" {
role = extractTraitString(traits, "role")
}
role = domain.NormalizeRole(role)
if role == "" {
role = domain.RoleUser
}
localUser.Role = role
if localUser.Status == "" {
localUser.Status = domain.UserStatusActive
}
if localUser.CreatedAt.IsZero() {
localUser.CreatedAt = now
}
coreTraits := map[string]bool{
"email": true, "name": true, "phone_number": true,
"grade": true, "companyCode": true, "company_code": true,
"companyCodes": true, "department": true,
"position": true, "jobTitle": true,
"affiliationType": true, "role": true,
"tenant_id": true, "relying_party_id": true,
"custom_login_ids": true, "id": true,
}
metadata := make(domain.JSONMap)
for k, v := range traits {
if !coreTraits[k] {
metadata[k] = v
}
}
localUser.Metadata = metadata
return localUser
}
func (h *AuthHandler) syncUpdatedKratosUserReadModel(ctx context.Context, identityID string, traits map[string]interface{}) error {
if h == nil || h.UserRepo == nil {
return nil
}
var existing *domain.User
if current, err := h.UserRepo.FindByID(ctx, identityID); err == nil {
existing = current
} else {
slog.Warn("[UpdateMe] Failed to load existing local user before read-model sync", "userID", identityID, "error", err)
}
localUser := h.mapKratosTraitsToLocalUser(identityID, traits, existing)
return h.UserRepo.Update(ctx, localUser)
}
func (h *AuthHandler) applySessionInfoFromWhoami(profile *domain.UserProfileResponse, authenticatedAt, usedIdentifier string) *domain.UserProfileResponse {
if profile == nil {
return nil
@@ -7374,9 +7486,10 @@ func (h *AuthHandler) UpdateMe(c *fiber.Ctx) error {
// [New] Local DB Sync - Sync synchronously to ensure immediate consistency
if h.UserRepo != nil {
ctx := context.Background()
// Also update local User record (read-model)
// We can fetch updated identity or just map current traits
// Since mapKratosIdentityToProfile is for UI, let's just use UpdateUserLoginIDs first
if err := h.syncUpdatedKratosUserReadModel(ctx, identityID, traits); err != nil {
slog.Error("[UpdateMe] Failed to sync local user read-model", "userID", identityID, "error", err)
markUserProjectionFailed(ctx, h.UserProjectionRepo, err)
}
if err := h.UserRepo.UpdateUserLoginIDs(ctx, identityID, loginIDRecords); err != nil {
slog.Error("[UpdateMe] Failed to update user login IDs", "userID", identityID, "error", err)
}