1
0
forked from baron/baron-sso

feat: implement CSV-based organization chart import and enhance group hierarchy

This commit is contained in:
2026-03-04 14:20:49 +09:00
parent a973cd746b
commit 9ac99d2e0c
4 changed files with 187 additions and 20 deletions

View File

@@ -81,6 +81,11 @@ func (s *orgChartService) ImportCSV(ctx context.Context, tenantID string, r io.R
orgPath := strings.TrimSpace(record[colMap["organization"]])
position := strings.TrimSpace(record[colMap["position"]])
jobTitle := strings.TrimSpace(record[colMap["jobtitle"]])
isOwner := false
if idx, ok := colMap["is_owner"]; ok && idx < len(record) {
val := strings.ToLower(record[idx])
isOwner = val == "true" || val == "y" || val == "1" || val == "yes"
}
if email == "" || name == "" || orgPath == "" {
continue
@@ -125,13 +130,25 @@ func (s *orgChartService) ImportCSV(ctx context.Context, tenantID string, r io.R
// 3. Sync Membership to Keto via Outbox
if s.ketoOutboxRepo != nil {
// Add as member of UserGroup
_ = s.ketoOutboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Namespace: "UserGroup",
Object: leafID,
Relation: "members",
Subject: "User:" + kratosID,
Action: domain.KetoOutboxActionCreate,
})
// Add as owner if applicable
if isOwner {
_ = s.ketoOutboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "UserGroup",
Object: leafID,
Relation: "owners",
Subject: "User:" + kratosID,
Action: domain.KetoOutboxActionCreate,
})
}
}
}
@@ -161,19 +178,19 @@ func (s *orgChartService) ensureOrgPath(ctx context.Context, rootTenantID string
}
// Check DB if already exists
// We search for a USER_GROUP tenant with this name and parent
// Note: This logic assumes name is unique under a parent
// For robustness, we should probably have a better lookup
var existingID string
// In a real implementation, Repo should have a FindByParentAndName method
// For this implementation, we'll try to find by Name and ParentID in TenantRepo or UserGroupRepo
// Since we're using Polymorphic Tenants, let's assume we can lookup
// For simplicity in this POC, let's just use Create logic if not in cache
// In production, we MUST check DB first to avoid duplicates
// [Placeholder] Lookup in DB logic...
// existingID = s.lookupOrgUnit(ctx, rootTenantID, currentParentID, part)
if s.userGroupRepo != nil {
groups, err := s.userGroupRepo.ListByTenantID(ctx, rootTenantID)
if err == nil {
for _, g := range groups {
// Match by name and parent
if g.Name == part && ((g.ParentID == nil && currentParentID == rootTenantID) || (g.ParentID != nil && *g.ParentID == currentParentID)) {
existingID = g.ID
break
}
}
}
}
if existingID == "" {
// Create new unit