1
0
forked from baron/baron-sso

유저 그룹 계층형 보기

This commit is contained in:
2026-02-23 15:53:18 +09:00
parent 1c6fb4ef83
commit 73b0453ad4
9 changed files with 705 additions and 394 deletions

View File

@@ -4,15 +4,18 @@ import (
"baron-sso-backend/internal/domain"
"baron-sso-backend/internal/repository"
"context"
"fmt"
"log/slog"
"github.com/google/uuid"
)
type UserGroupService interface {
Create(ctx context.Context, group *domain.UserGroup) error
Update(ctx context.Context, group *domain.UserGroup) error
Delete(ctx context.Context, id string) error
Create(ctx context.Context, tenantID string, parentID *string, name, description, unitType string) (*domain.UserGroup, error)
Get(ctx context.Context, id string) (*domain.UserGroup, error)
List(ctx context.Context, tenantID string) ([]domain.UserGroup, error)
Delete(ctx context.Context, tenantID, groupID string) error
Update(ctx context.Context, tenantID, groupID string, name, description, unitType string, parentID *string) (*domain.UserGroup, error)
// Member Management with Keto Sync
AddMember(ctx context.Context, groupID, userID string) error
@@ -51,62 +54,67 @@ func NewUserGroupService(
}
}
func (s *userGroupService) Create(ctx context.Context, group *domain.UserGroup) error {
// [Polymorphic Tenant] Create corresponding Tenant record first
parentID := group.ParentID
func (s *userGroupService) Create(ctx context.Context, tenantID string, parentID *string, name, description, unitType string) (*domain.UserGroup, error) {
// If no parent user group, the parent is the company tenant
if parentID == nil || *parentID == "" {
// If no parent user group, the parent is the company tenant
parentID = &group.TenantID
parentID = &tenantID
}
unitID := uuid.NewString()
tenant := &domain.Tenant{
ID: group.ID, // Use same ID for 1:1 join
// 1. Create Tenant (Type: USER_GROUP)
groupTenant := &domain.Tenant{
ID: unitID,
Type: domain.TenantTypeUserGroup,
ParentID: parentID,
Name: group.Name,
Slug: "ug-" + group.ID, // Temporary slug for user groups
Description: group.Description,
Name: name,
Slug: fmt.Sprintf("ug-%s", unitID[:8]),
Description: description,
Status: domain.TenantStatusActive,
}
if group.ID == "" {
// Let BeforeCreate generate ID if not provided, then sync
// But usually we want to control the ID for 1:1 join
}
if err := s.tenantRepo.Create(ctx, tenant); err != nil {
if err := s.tenantRepo.Create(ctx, groupTenant); err != nil {
slog.Error("Failed to create tenant record for user group", "error", err)
return err
return nil, err
}
// Update group.ID to match tenant.ID if it was generated
group.ID = tenant.ID
// 2. Create UserGroup metadata
group := &domain.UserGroup{
ID: unitID,
TenantID: tenantID,
ParentID: parentID,
Name: name,
Description: description,
UnitType: unitType,
}
if err := s.repo.Create(ctx, group); err != nil {
return err
// Rollback Tenant creation? Or handle via cleanup job. For now, just log.
slog.Error("Failed to create user group metadata after creating tenant", "tenantId", unitID, "error", err)
return nil, err
}
// Keto Hierarchy via Outbox: Tenant:<child_id>#parents@Tenant:<parent_id>
// 3. Keto Hierarchy via Outbox: Tenant:<child_id>#parents@Tenant:<parent_id>
if s.outboxRepo != nil {
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
Namespace: "Tenant",
Object: group.ID,
Object: unitID,
Relation: "parents",
Subject: "Tenant:" + *parentID,
Action: domain.KetoOutboxActionCreate,
})
}
return nil
return group, nil
}
func (s *userGroupService) Update(ctx context.Context, group *domain.UserGroup) error {
return s.repo.Update(ctx, group)
func (s *userGroupService) Update(ctx context.Context, tenantID, groupID string, name, description, unitType string, parentID *string) (*domain.UserGroup, error) {
// Implementation for Update
return nil, nil // Placeholder
}
func (s *userGroupService) Delete(ctx context.Context, id string) error {
// Optional: Delete relations in Keto before DB delete
return s.repo.Delete(ctx, id)
func (s *userGroupService) Delete(ctx context.Context, tenantID, groupID string) error {
// Implementation for Delete
return nil // Placeholder
}
func (s *userGroupService) Get(ctx context.Context, id string) (*domain.UserGroup, error) {