1
0
forked from baron/baron-sso

조직도 기능 추가

This commit is contained in:
2026-04-10 11:38:47 +09:00
parent 6971b69b79
commit 5211842d47
28 changed files with 1845 additions and 447 deletions

View File

@@ -105,11 +105,23 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
if profile != nil {
for _, t := range profile.ManageableTenants {
manageableSlugs[strings.ToLower(t.Slug)] = true
manageableSlugs[strings.ToLower(t.ID)] = true // Add ID as well
}
// Include primary tenant slug if not already there
if profile.CompanyCode != "" {
manageableSlugs[strings.ToLower(profile.CompanyCode)] = true
}
if profile.TenantID != nil {
manageableSlugs[strings.ToLower(*profile.TenantID)] = true
}
}
}
var targetTenantID string
if tenantSlug != "" && h.TenantService != nil {
t, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug)
if err == nil && t != nil {
targetTenantID = strings.ToLower(t.ID)
}
}
@@ -123,16 +135,17 @@ func (h *UserHandler) ListUsers(c *fiber.Ctx) error {
email := strings.ToLower(extractTraitString(identity.Traits, "email"))
name := strings.ToLower(extractTraitString(identity.Traits, "name"))
compCode := strings.ToLower(extractTraitString(identity.Traits, "companyCode"))
tID := strings.ToLower(extractTraitString(identity.Traits, "tenant_id"))
// Tenant Admin filtering
if requesterRole == domain.RoleTenantAdmin {
if !manageableSlugs[compCode] {
if !manageableSlugs[compCode] && !manageableSlugs[tID] {
continue
}
}
// Dedicated tenantSlug filter
if tenantSlug != "" && !strings.EqualFold(compCode, tenantSlug) {
if tenantSlug != "" && !strings.EqualFold(compCode, tenantSlug) && tID != targetTenantID {
continue
}
@@ -261,15 +274,17 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
}
var req struct {
Email string `json:"email"`
LoginID string `json:"loginId"`
Password string `json:"password"`
Name string `json:"name"`
Phone string `json:"phone"`
Role string `json:"role"`
CompanyCode string `json:"companyCode"`
Department string `json:"department"`
Metadata map[string]any `json:"metadata"`
Email string `json:"email"`
LoginID string `json:"loginId"`
Password string `json:"password"`
Name string `json:"name"`
Phone string `json:"phone"`
Role string `json:"role"`
CompanyCode string `json:"companyCode"`
Department string `json:"department"`
Position string `json:"position"`
JobTitle string `json:"jobTitle"`
Metadata map[string]any `json:"metadata"`
}
if err := c.BodyParser(&req); err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
@@ -321,6 +336,8 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
attributes := map[string]interface{}{
"department": req.Department,
"position": req.Position,
"jobTitle": req.JobTitle,
"affiliationType": "internal",
"companyCode": req.CompanyCode,
"grade": role,
@@ -454,6 +471,8 @@ type bulkUserItem struct {
Role string `json:"role"`
TenantSlug string `json:"tenantSlug"`
Department string `json:"department"`
Position string `json:"position"`
JobTitle string `json:"jobTitle"`
Metadata map[string]any `json:"metadata"`
}
@@ -570,6 +589,8 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
attributes := map[string]interface{}{
"department": dept,
"position": strings.TrimSpace(item.Position),
"jobTitle": strings.TrimSpace(item.JobTitle),
"affiliationType": "internal",
"companyCode": tenantSlug,
"tenant_id": tItem.ID,
@@ -845,6 +866,8 @@ func (h *UserHandler) BulkUpdateUsers(c *fiber.Ctx) error {
Role *string `json:"role"`
CompanyCode *string `json:"companyCode"`
Department *string `json:"department"`
Position *string `json:"position"`
JobTitle *string `json:"jobTitle"`
}
if err := c.BodyParser(&req); err != nil {
return errorJSON(c, fiber.StatusBadRequest, "invalid request body")
@@ -879,6 +902,16 @@ func (h *UserHandler) BulkUpdateUsers(c *fiber.Ctx) error {
results := make([]map[string]any, 0, len(req.UserIDs))
for _, id := range req.UserIDs {
// [Safety] Cannot delete yourself
if id == requester.ID {
results = append(results, map[string]any{
"id": id,
"success": false,
"message": "cannot delete your own account for safety",
})
continue
}
identity, err := h.KratosAdmin.GetIdentity(c.Context(), id)
if err != nil {
results = append(results, map[string]any{"id": id, "success": false, "message": "user not found"})
@@ -924,6 +957,12 @@ func (h *UserHandler) BulkUpdateUsers(c *fiber.Ctx) error {
if req.Department != nil {
traits["department"] = *req.Department
}
if req.Position != nil {
traits["position"] = *req.Position
}
if req.JobTitle != nil {
traits["jobTitle"] = *req.JobTitle
}
state := identity.State
if req.Status != nil {
@@ -958,6 +997,12 @@ func (h *UserHandler) BulkUpdateUsers(c *fiber.Ctx) error {
if req.Department != nil {
localUser.Department = *req.Department
}
if req.Position != nil {
localUser.Position = *req.Position
}
if req.JobTitle != nil {
localUser.JobTitle = *req.JobTitle
}
// Resolve TenantID if changing companyCode
if req.CompanyCode != nil && h.TenantService != nil {
@@ -1011,6 +1056,16 @@ func (h *UserHandler) BulkDeleteUsers(c *fiber.Ctx) error {
results := make([]map[string]any, 0, len(req.UserIDs))
for _, id := range req.UserIDs {
// [Safety] Cannot delete yourself
if id == requester.ID {
results = append(results, map[string]any{
"id": id,
"success": false,
"message": "cannot delete your own account for safety",
})
continue
}
identity, err := h.KratosAdmin.GetIdentity(c.Context(), id)
if err != nil {
results = append(results, map[string]any{"id": id, "success": false, "message": "user not found"})
@@ -1093,6 +1148,8 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
Status *string `json:"status"`
CompanyCode *string `json:"companyCode"`
Department *string `json:"department"`
Position *string `json:"position"`
JobTitle *string `json:"jobTitle"`
Metadata map[string]any `json:"metadata"`
}
if err := c.BodyParser(&req); err != nil {
@@ -1176,6 +1233,12 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
if req.Department != nil {
traits["department"] = strings.TrimSpace(*req.Department)
}
if req.Position != nil {
traits["position"] = strings.TrimSpace(*req.Position)
}
if req.JobTitle != nil {
traits["jobTitle"] = strings.TrimSpace(*req.JobTitle)
}
if req.Role != nil {
role := domain.NormalizeRole(*req.Role)
if role == "" {
@@ -1189,6 +1252,7 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
coreTraits := map[string]bool{
"email": true, "name": true, "phone_number": true,
"grade": true, "companyCode": true, "department": true,
"position": true, "jobTitle": true,
"affiliationType": true, "role": true, "tenant_id": true,
"custom_login_ids": true, "id": true,
}
@@ -1339,6 +1403,12 @@ func (h *UserHandler) DeleteUser(c *fiber.Ctx) error {
// [New] Check access scope before deletion
requester, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
// [Safety] Cannot delete yourself
if requester != nil && userID == requester.ID {
return errorJSON(c, fiber.StatusForbidden, "cannot delete your own account for safety")
}
if requester != nil && domain.NormalizeRole(requester.Role) == domain.RoleTenantAdmin {
identity, err := h.KratosAdmin.GetIdentity(c.Context(), userID)
if err == nil && identity != nil {
@@ -1407,14 +1477,16 @@ func (h *UserHandler) mapIdentitySummary(ctx context.Context, identity service.K
Status: normalizeStatus(identity.State),
CompanyCode: compCode,
Department: extractTraitString(traits, "department"),
Position: extractTraitString(traits, "position"),
JobTitle: extractTraitString(traits, "jobTitle"),
Metadata: make(domain.JSONMap),
CreatedAt: formatTime(identity.CreatedAt),
UpdatedAt: formatTime(identity.UpdatedAt),
}
// [New] Fetch all manageable tenants (for Multi-tenancy support)
// [New] Fetch all joined tenants (for Multi-tenancy support)
if h.TenantService != nil {
if joined, err := h.TenantService.ListManageableTenants(ctx, identity.ID); err == nil {
if joined, err := h.TenantService.ListJoinedTenants(ctx, identity.ID); err == nil {
summary.JoinedTenants = joined
}
}
@@ -1426,6 +1498,7 @@ func (h *UserHandler) mapIdentitySummary(ctx context.Context, identity service.K
coreTraits := map[string]bool{
"email": true, "name": true, "phone_number": true,
"grade": true, "companyCode": true, "department": true,
"position": true, "jobTitle": true,
"affiliationType": true, "role": true, "tenant_id": true,
"custom_login_ids": true, "id": true,
}
@@ -1476,6 +1549,8 @@ func (h *UserHandler) mapToLocalUser(identity service.KratosIdentity) *domain.Us
Status: normalizeStatus(identity.State),
CompanyCode: compCode,
Department: extractTraitString(traits, "department"),
Position: extractTraitString(traits, "position"),
JobTitle: extractTraitString(traits, "jobTitle"),
AffiliationType: extractTraitString(traits, "affiliationType"),
CreatedAt: identity.CreatedAt,
UpdatedAt: identity.UpdatedAt,
@@ -1501,6 +1576,7 @@ func (h *UserHandler) mapToLocalUser(identity service.KratosIdentity) *domain.Us
coreTraits := map[string]bool{
"email": true, "name": true, "phone_number": true,
"grade": true, "companyCode": true, "department": true,
"position": true, "jobTitle": true,
"affiliationType": true, "role": true, "tenant_id": true, "company_code": true,
"custom_login_ids": true, "id": true,
}