From bc73b8590968d7c4e479e032653cbb7b7a3865b6 Mon Sep 17 00:00:00 2001 From: chan Date: Tue, 31 Mar 2026 13:50:23 +0900 Subject: [PATCH] feat(backend): auto-sync user group keto relation based on department in user update --- backend/internal/handler/user_handler.go | 48 +++++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/backend/internal/handler/user_handler.go b/backend/internal/handler/user_handler.go index e3ad9a85..956b47ca 100644 --- a/backend/internal/handler/user_handler.go +++ b/backend/internal/handler/user_handler.go @@ -1042,9 +1042,11 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error { // Capture current local state for transition comparison var oldRole string var oldTenantID string + var oldDepartment string if h.UserRepo != nil { if local, err := h.UserRepo.FindByID(c.Context(), userID); err == nil && local != nil { oldRole = local.Role + oldDepartment = local.Department if local.TenantID != nil { oldTenantID = *local.TenantID } @@ -1237,8 +1239,50 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error { } // [Keto Sync] asynchronously as it's less critical for immediate UI count - go h.syncKetoRole(context.Background(), updatedLocalUser.ID, - extractTraitString(updated.Traits, "grade"), oldRole, oldTenantID, updatedLocalUser.TenantID) + go func() { + bgCtx := context.Background() + h.syncKetoRole(bgCtx, updatedLocalUser.ID, + extractTraitString(updated.Traits, "grade"), oldRole, oldTenantID, updatedLocalUser.TenantID) + + // Try to automatically sync UserGroup membership based on Department + if h.UserGroupRepo != nil && h.KetoOutboxRepo != nil { + // 1. Remove from old group if department or tenant changed + if oldTenantID != "" && oldDepartment != "" && (oldTenantID != extractTraitString(updated.Traits, "tenant_id") || oldDepartment != updatedLocalUser.Department) { + if oldGroups, err := h.UserGroupRepo.ListByTenantID(bgCtx, oldTenantID); err == nil { + for _, g := range oldGroups { + if strings.EqualFold(g.Name, oldDepartment) { + _ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{ + Namespace: "Tenant", + Object: g.ID, + Relation: "members", + Subject: "User:" + updatedLocalUser.ID, + Action: domain.KetoOutboxActionDelete, + }) + break + } + } + } + } + + // 2. Add to new group + if updatedLocalUser.TenantID != nil && updatedLocalUser.Department != "" { + if groups, err := h.UserGroupRepo.ListByTenantID(bgCtx, *updatedLocalUser.TenantID); err == nil { + for _, g := range groups { + if strings.EqualFold(g.Name, updatedLocalUser.Department) { + _ = h.KetoOutboxRepo.Create(bgCtx, &domain.KetoOutbox{ + Namespace: "Tenant", + Object: g.ID, + Relation: "members", + Subject: "User:" + updatedLocalUser.ID, + Action: domain.KetoOutboxActionCreate, + }) + break + } + } + } + } + } + }() } if req.Password != nil && *req.Password != "" {