package bootstrap import ( "baron-sso-backend/internal/domain" "baron-sso-backend/internal/repository" "baron-sso-backend/internal/service" "context" "errors" "fmt" "log/slog" "gorm.io/gorm" ) type InitialTenantConfig struct { Name string Slug string Type string ParentSlug string Description string Domains []string } // Hardcoded for now, can be moved to config file or env later var defaultTenants = []InitialTenantConfig{ { Name: "한맥가족", Slug: "hanmac-family", Type: domain.TenantTypeCompanyGroup, }, { Name: "한맥기술", Slug: "hanmac", Type: domain.TenantTypeCompany, ParentSlug: "hanmac-family", Description: "Primary Family Company", Domains: []string{"hanmaceng.co.kr", "hmac.kr"}, }, { Name: "삼안", Slug: "saman", Type: domain.TenantTypeCompany, ParentSlug: "hanmac-family", Domains: []string{"samaneng.com"}, }, } func SeedTenants(db *gorm.DB) error { slog.Info("[Bootstrap] Seeding initial tenants...") repo := repository.NewTenantRepository(db) userRepo := repository.NewUserRepository(db) userGroupRepo := repository.NewUserGroupRepository(db) outboxRepo := repository.NewKetoOutboxRepository(db) svc := service.NewTenantService(repo, userRepo, userGroupRepo, outboxRepo) ctx := context.Background() for _, config := range defaultTenants { tenantType := config.Type if tenantType == "" { tenantType = domain.TenantTypeCompany } var parentID *string if config.ParentSlug != "" { parent, err := repo.FindBySlug(ctx, config.ParentSlug) if err != nil || parent == nil { if err == nil { err = errors.New("parent tenant not found") } slog.Error("Failed to resolve parent tenant for seed", "slug", config.Slug, "parentSlug", config.ParentSlug, "error", err) return fmt.Errorf("resolve parent tenant %q for seed %q: %w", config.ParentSlug, config.Slug, err) } parentID = &parent.ID } existing, err := repo.FindBySlug(ctx, config.Slug) if err == nil && existing != nil { slog.Info("[Bootstrap] Tenant already exists, checking domains...", "slug", config.Slug) changed := false if existing.Name != config.Name { existing.Name = config.Name changed = true } if existing.Type != tenantType { existing.Type = tenantType changed = true } if existing.Status != domain.TenantStatusActive { existing.Status = domain.TenantStatusActive changed = true } if config.ParentSlug != "" { if existing.ParentID == nil || *existing.ParentID != *parentID { existing.ParentID = parentID changed = true if err := outboxRepo.Create(ctx, &domain.KetoOutbox{ Namespace: "Tenant", Object: existing.ID, Relation: "parents", Subject: "Tenant:" + *parentID, Action: domain.KetoOutboxActionCreate, }); err != nil { slog.Error("Failed to create outbox entry for seeded tenant hierarchy", "tenant", existing.ID, "error", err) return err } } } else if existing.ParentID != nil { existing.ParentID = nil changed = true } if changed { if err := repo.Update(ctx, existing); err != nil { slog.Error("Failed to update seeded tenant", "slug", config.Slug, "error", err) return err } } // Optional: Check and add missing domains for _, d := range config.Domains { found := false for _, ed := range existing.Domains { if ed.Domain == d { found = true break } } if !found { slog.Info("[Bootstrap] Adding missing domain to tenant", "slug", config.Slug, "domain", d) if err := repo.AddDomain(ctx, existing.ID, d, true); err != nil { slog.Error("Failed to add domain", "error", err) } } } continue } slog.Info("[Bootstrap] Creating default tenant", "name", config.Name, "slug", config.Slug) tenant, err := svc.RegisterTenant(ctx, config.Name, config.Slug, tenantType, config.Description, config.Domains, parentID, "") if err != nil { slog.Error("Failed to seed tenant", "slug", config.Slug, "error", err) return err } // Explicitly set to active during seed tenant.Status = domain.TenantStatusActive db.Save(tenant) } return nil }