package bootstrap import ( "baron-sso-backend/internal/domain" "fmt" "log/slog" "gorm.io/gorm" ) // Run executes the application bootstrap logic (migrations, seeding, etc.) func Run(db *gorm.DB) error { slog.Info("[Bootstrap] Starting application bootstrap...") // 1. Auto Migration if err := migrateSchemas(db); err != nil { return fmt.Errorf("migration failed: %w", err) } // 2. Seed Tenants if err := SeedTenants(db); err != nil { return fmt.Errorf("tenant seeding failed: %w", err) } // 3. Normalize staging seed/read-model data if err := CanonicalizeLegacyUserStatuses(db); err != nil { return fmt.Errorf("legacy user status canonicalization failed: %w", err) } if err := SanitizeLegacyUserMetadata(db); err != nil { return fmt.Errorf("legacy user metadata sanitize failed: %w", err) } slog.Info("[Bootstrap] User seed skipped (Kratos is SoT)") slog.Info("[Bootstrap] Bootstrap completed successfully.") return nil } func migrateSchemas(db *gorm.DB) error { slog.Info("[Bootstrap] Migrating database schemas...") if err := dropLegacyTenantDomainUniqueIndex(db); err != nil { return err } if err := dropLegacyUserCompanyColumns(db); err != nil { return err } // Add all domain models here return db.AutoMigrate( &domain.Tenant{}, &domain.TenantDomain{}, &domain.User{}, &domain.UserLoginID{}, &domain.UserProjectionState{}, &domain.UserGroup{}, &domain.ApiKey{}, &domain.IdentityProviderConfig{}, &domain.ClientSecret{}, &domain.ClientConsent{}, &domain.KetoOutbox{}, &domain.RPUsageEvent{}, &domain.WorksmobileOutbox{}, &domain.WorksmobileResourceMapping{}, &domain.SharedLink{}, &domain.DeveloperRequest{}, &domain.RPUserMetadata{}, &domain.SystemSetting{}, // &domain.RelyingParty{}, // Removed: SSOT is Hydra + Keto ) } func CanonicalizeLegacyUserStatuses(db *gorm.DB) error { if db == nil || !db.Migrator().HasTable(&domain.User{}) { return nil } updates := map[string]string{ "inactive": domain.UserStatusPreboarding, "leave_of_absence": domain.UserStatusTemporaryLeave, "baron_only": domain.UserStatusBaronGuest, } for legacy, canonical := range updates { if err := db.Model(&domain.User{}). Where("status = ?", legacy). Update("status", canonical).Error; err != nil { return fmt.Errorf("failed to canonicalize users.status %s to %s: %w", legacy, canonical, err) } } return nil } func dropLegacyUserCompanyColumns(db *gorm.DB) error { if !db.Migrator().HasTable(&domain.User{}) { return nil } for _, column := range []string{"company_code", "company_codes"} { if !db.Migrator().HasColumn(&domain.User{}, column) { continue } if err := db.Migrator().DropColumn(&domain.User{}, column); err != nil { return fmt.Errorf("failed to drop legacy users.%s column: %w", column, err) } } return nil } func dropLegacyTenantDomainUniqueIndex(db *gorm.DB) error { if !db.Migrator().HasTable(&domain.TenantDomain{}) { return nil } if !db.Migrator().HasIndex(&domain.TenantDomain{}, "idx_tenant_domains_domain") { return nil } if err := db.Migrator().DropIndex(&domain.TenantDomain{}, "idx_tenant_domains_domain"); err != nil { return fmt.Errorf("failed to drop legacy tenant domain unique index: %w", err) } return nil }