sendInviteEmails sends invite emails to newly added members. Errors are logged, never returned.
(ctx context.Context, workspaceID string, oldPolicy *storepb.IamPolicy, deltas []*v1pb.BindingDelta)
| 469 | |
| 470 | // sendInviteEmails sends invite emails to newly added members. Errors are logged, never returned. |
| 471 | func (s *WorkspaceService) sendInviteEmails(ctx context.Context, workspaceID string, oldPolicy *storepb.IamPolicy, deltas []*v1pb.BindingDelta) { |
| 472 | emailSetting, err := resolvePreLoginEmailSetting(ctx, s.store, workspaceID) |
| 473 | if err != nil || emailSetting == nil { |
| 474 | return |
| 475 | } |
| 476 | |
| 477 | sender, err := mailer.NewSender(emailSetting) |
| 478 | if err != nil { |
| 479 | slog.Warn("failed to create mail sender for invite emails", log.BBError(err)) |
| 480 | return |
| 481 | } |
| 482 | |
| 483 | // Get workspace URL and title. |
| 484 | externalURL, err := utils.GetEffectiveExternalURL(ctx, s.store, s.profile, workspaceID) |
| 485 | if err != nil { |
| 486 | slog.Warn("failed to get external URL for invite emails", log.BBError(err)) |
| 487 | return |
| 488 | } |
| 489 | if externalURL == "" { |
| 490 | slog.Warn("empty external URL, skip send invite emails") |
| 491 | return |
| 492 | } |
| 493 | workspaceTitle := workspaceID |
| 494 | if ws, err := s.store.GetWorkspaceByID(ctx, workspaceID); err == nil && ws != nil && ws.Payload.GetTitle() != "" { |
| 495 | workspaceTitle = ws.Payload.GetTitle() |
| 496 | } |
| 497 | |
| 498 | // Build set of members who already existed in the old policy (any role). |
| 499 | existingMembers := make(map[string]bool) |
| 500 | for _, binding := range oldPolicy.GetBindings() { |
| 501 | for _, member := range binding.Members { |
| 502 | if email, ok := strings.CutPrefix(member, common.UserNamePrefix); ok { |
| 503 | existingMembers[email] = true |
| 504 | } else if groupName, ok := strings.CutPrefix(member, common.GroupPrefix); ok { |
| 505 | // Expand group to individual members. |
| 506 | group, err := s.store.GetGroupByName(ctx, workspaceID, common.GroupPrefix+groupName) |
| 507 | if err != nil || group == nil || group.Payload == nil { |
| 508 | continue |
| 509 | } |
| 510 | for _, m := range group.Payload.Members { |
| 511 | if email, ok := strings.CutPrefix(m.Member, common.UserNamePrefix); ok { |
| 512 | existingMembers[email] = true |
| 513 | } |
| 514 | } |
| 515 | } |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | // Collect truly new user emails and their roles. Map deduplicates by email. |
| 520 | invites := make(map[string]string) // email -> roleName |
| 521 | for _, delta := range deltas { |
| 522 | if delta.Action != v1pb.BindingDelta_ADD { |
| 523 | continue |
| 524 | } |
| 525 | roleName := delta.Role |
| 526 | if role, err := s.store.GetRole(ctx, &store.FindRoleMessage{Workspace: workspaceID, ResourceID: &delta.Role}); err == nil && role != nil { |
| 527 | roleName = role.Name |
| 528 | } |
no test coverage detected