forked from baron/baron-sso
adminfront 조직 통계오류 보정. Kratos Projection용 통계테이블 구조 추가
This commit is contained in:
@@ -28,15 +28,16 @@ type OryProviderAPI interface {
|
||||
}
|
||||
|
||||
type UserHandler struct {
|
||||
KratosAdmin service.KratosAdminService
|
||||
OryProvider OryProviderAPI
|
||||
TenantService service.TenantService
|
||||
KetoService service.KetoService
|
||||
KetoOutboxRepo repository.KetoOutboxRepository
|
||||
UserRepo repository.UserRepository
|
||||
UserGroupRepo repository.UserGroupRepository
|
||||
AuditRepo domain.AuditRepository
|
||||
Worksmobile service.WorksmobileSyncer
|
||||
KratosAdmin service.KratosAdminService
|
||||
OryProvider OryProviderAPI
|
||||
TenantService service.TenantService
|
||||
KetoService service.KetoService
|
||||
KetoOutboxRepo repository.KetoOutboxRepository
|
||||
UserRepo repository.UserRepository
|
||||
UserProjectionRepo repository.UserProjectionRepository
|
||||
UserGroupRepo repository.UserGroupRepository
|
||||
AuditRepo domain.AuditRepository
|
||||
Worksmobile service.WorksmobileSyncer
|
||||
}
|
||||
|
||||
func NewUserHandler(kratosAdmin service.KratosAdminService, oryProvider OryProviderAPI, tenantService service.TenantService, ketoService service.KetoService, ketoOutboxRepo repository.KetoOutboxRepository, userRepo repository.UserRepository, userGroupRepo repository.UserGroupRepository, auditRepo domain.AuditRepository) *UserHandler {
|
||||
@@ -265,7 +266,10 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Try Kratos First
|
||||
if h.KratosAdmin == nil {
|
||||
return errorJSON(c, fiber.StatusServiceUnavailable, "identity provider not available")
|
||||
}
|
||||
|
||||
identities, err := h.KratosAdmin.ListIdentities(c.Context())
|
||||
if err == nil {
|
||||
filtered := make([]service.KratosIdentity, 0, len(identities))
|
||||
@@ -363,46 +367,8 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
|
||||
return c.JSON(userListResponse{Items: items, Limit: limit, Offset: offset, Total: total})
|
||||
}
|
||||
|
||||
// 2. Fallback to Local DB if Kratos is down
|
||||
slog.Warn("Kratos unavailable, falling back to local DB for user list", "error", err)
|
||||
|
||||
// If requester is not Super Admin, we should technically filter by manageable slugs in DB too.
|
||||
// For simplicity in fallback, if tenantSlug is empty we default to their primary company code.
|
||||
if (requesterRole == domain.RoleTenantAdmin || requesterRole == domain.RoleUser || requesterRole == domain.RoleRPAdmin) && tenantSlug == "" {
|
||||
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
|
||||
if profile != nil && profile.CompanyCode != "" {
|
||||
tenantSlug = profile.CompanyCode
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from UserRepo
|
||||
users, total, err := h.UserRepo.List(c.Context(), offset, limit, search, tenantSlug)
|
||||
if err != nil {
|
||||
return errorJSON(c, fiber.StatusInternalServerError, "failed to fetch users from both kratos and local db")
|
||||
}
|
||||
|
||||
items := make([]userSummary, 0, len(users))
|
||||
for _, u := range users {
|
||||
items = append(items, userSummary{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
Name: u.Name,
|
||||
Phone: u.Phone,
|
||||
Role: u.Role,
|
||||
Status: u.Status,
|
||||
CompanyCode: u.CompanyCode,
|
||||
Department: u.Department,
|
||||
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(userListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
slog.Warn("Kratos unavailable for user list", "error", err)
|
||||
return errorJSON(c, fiber.StatusServiceUnavailable, "identity provider unavailable")
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUser(c *fiber.Ctx) error {
|
||||
@@ -632,6 +598,7 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
||||
// Sync to local DB (Synchronous for immediate consistency)
|
||||
if err := h.UserRepo.Update(c.Context(), localUser); err != nil {
|
||||
slog.Error("[UserHandler] Failed to sync new user to local DB", "email", localUser.Email, "error", err)
|
||||
markUserProjectionFailed(c.Context(), h.UserProjectionRepo, err)
|
||||
}
|
||||
if h.Worksmobile != nil {
|
||||
if err := h.Worksmobile.EnqueueUserUpsertIfInScope(c.Context(), *localUser); err != nil {
|
||||
@@ -645,6 +612,7 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
if err := h.UserRepo.UpdateUserLoginIDs(c.Context(), localUser.ID, loginIDRecords); err != nil {
|
||||
slog.Error("[UserHandler] Failed to update user login IDs", "userID", localUser.ID, "error", err)
|
||||
markUserProjectionFailed(c.Context(), h.UserProjectionRepo, err)
|
||||
}
|
||||
|
||||
// [Keto] Sync relations via Outbox (Synchronous for accurate counting)
|
||||
@@ -938,6 +906,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
|
||||
|
||||
if err := h.UserRepo.Update(c.Context(), localUser); err != nil {
|
||||
slog.Error("Failed to sync bulk user to local DB", "email", email, "error", err)
|
||||
markUserProjectionFailed(c.Context(), h.UserProjectionRepo, err)
|
||||
}
|
||||
if h.Worksmobile != nil {
|
||||
if err := h.Worksmobile.EnqueueUserUpsertIfInScope(c.Context(), *localUser); err != nil {
|
||||
@@ -951,6 +920,7 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
|
||||
}
|
||||
if err := h.UserRepo.UpdateUserLoginIDs(c.Context(), localUser.ID, loginIDRecords); err != nil {
|
||||
slog.Error("Failed to update user login IDs in bulk", "userID", localUser.ID, "error", err)
|
||||
markUserProjectionFailed(c.Context(), h.UserProjectionRepo, err)
|
||||
}
|
||||
|
||||
if h.KetoOutboxRepo != nil {
|
||||
@@ -1769,6 +1739,7 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
ctx := context.Background() // Use request context if appropriate, but sync must finish
|
||||
if err := h.UserRepo.Update(ctx, updatedLocalUser); err != nil {
|
||||
slog.Error("[UserHandler] Failed to sync updated user to local DB", "userID", updatedLocalUser.ID, "error", err)
|
||||
markUserProjectionFailed(ctx, h.UserProjectionRepo, err)
|
||||
}
|
||||
if h.Worksmobile != nil {
|
||||
if err := h.Worksmobile.EnqueueUserUpsertIfInScope(ctx, *updatedLocalUser); err != nil {
|
||||
@@ -1779,6 +1750,7 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
// Update User Login IDs in local DB
|
||||
if err := h.UserRepo.UpdateUserLoginIDs(ctx, updatedLocalUser.ID, loginIDRecords); err != nil {
|
||||
slog.Error("[UserHandler] Failed to update user login IDs", "userID", updatedLocalUser.ID, "error", err)
|
||||
markUserProjectionFailed(ctx, h.UserProjectionRepo, err)
|
||||
}
|
||||
|
||||
// [Keto Sync] asynchronously as it's less critical for immediate UI count
|
||||
@@ -1927,6 +1899,13 @@ func (h *UserHandler) DeleteUser(c *fiber.Ctx) error {
|
||||
// Additional cleanup for tenants could be added here if we keep track of user's current tenants
|
||||
}
|
||||
|
||||
if h.UserRepo != nil {
|
||||
if err := h.UserRepo.Delete(context.Background(), userID); err != nil {
|
||||
slog.Error("[UserHandler] Failed to delete local user read-model", "userID", userID, "error", err)
|
||||
markUserProjectionFailed(context.Background(), h.UserProjectionRepo, err)
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user