package migration import ( "context" "strings" ) // OnboardingHints guides the user-facing migration setup flow. type OnboardingHints struct { NeedsUserOAuth bool `json:"needs_user_oauth"` OAuthProvider string `json:"oauth_provider,omitempty"` WaitingForAdmin bool `json:"waiting_for_admin"` WaitingReason string `json:"waiting_reason,omitempty"` HasMigrationCredentials bool `json:"has_migration_credentials"` NeedsMicrosoftAdminConsent bool `json:"needs_microsoft_admin_consent,omitempty"` } func (s *Service) BuildOnboardingHints(ctx context.Context, userID string, proj Project, invite Invite) OnboardingHints { h := OnboardingHints{ OAuthProvider: proj.SourceProvider, } switch proj.Status { case "active", "cutover": // worker eligible default: h.WaitingForAdmin = true if proj.Status == "draft" { h.WaitingReason = "project_not_activated" } else { h.WaitingReason = "project_status_" + proj.Status } } if UsesUserOAuth(proj.SourceProvider, proj.AuthMode) { h.NeedsUserOAuth = true if userID != "" { hasCred, err := s.hasMigrationCredential(ctx, userID, proj.ID, proj.SourceProvider) if err == nil { h.HasMigrationCredentials = hasCred } } } if proj.SourceProvider == "microsoft" && proj.MicrosoftAdminConsentAt == nil { h.NeedsMicrosoftAdminConsent = true } if invite.Status == "claimed" && h.WaitingForAdmin { h.WaitingReason = waitingReasonMessage(h.WaitingReason) } return h } func waitingReasonMessage(code string) string { switch code { case "project_not_activated": return "project_not_activated" default: return code } } func (s *Service) hasMigrationCredential(ctx context.Context, userID, projectID, provider string) (bool, error) { provider = strings.ToLower(strings.TrimSpace(provider)) if provider == "" { return false, nil } var exists bool err := s.db.QueryRow(ctx, ` SELECT EXISTS( SELECT 1 FROM migration_credentials WHERE user_id = $1::uuid AND project_id = $2::uuid AND provider = $3 AND revoked_at IS NULL ) `, userID, projectID, provider).Scan(&exists) return exists, err } func (s *Service) BuildInviteOnboardingHints(proj Project, invite Invite) OnboardingHints { h := OnboardingHints{OAuthProvider: proj.SourceProvider} if invite.Status == "claimed" { h.NeedsUserOAuth = UsesUserOAuth(proj.SourceProvider, proj.AuthMode) return h } if UsesUserOAuth(proj.SourceProvider, proj.AuthMode) { h.NeedsUserOAuth = true } if proj.SourceProvider == "microsoft" && proj.MicrosoftAdminConsentAt == nil { h.NeedsMicrosoftAdminConsent = true } return h }