package discovery import ( "context" "fmt" "strings" "github.com/jackc/pgx/v5" ) func (s *Service) relatedProfileIDs(ctx context.Context, externalUserID, profileID string) ([]string, error) { var groupID *string err := s.db.QueryRow(ctx, ` SELECT person_group_id::text FROM contact_discovered_profiles p JOIN users u ON p.user_id = u.id WHERE u.external_id = $1 AND p.id = $2::uuid `, externalUserID, profileID).Scan(&groupID) if err != nil { if err == pgx.ErrNoRows { return nil, fmt.Errorf("profile not found") } return nil, err } if groupID != nil && strings.TrimSpace(*groupID) != "" { rows, err := s.db.Query(ctx, ` SELECT p.id::text FROM contact_discovered_profiles p JOIN users u ON p.user_id = u.id WHERE u.external_id = $1 AND p.person_group_id = $2::uuid `, externalUserID, *groupID) if err != nil { return nil, err } defer rows.Close() var ids []string for rows.Next() { var id string if err := rows.Scan(&id); err != nil { return nil, err } ids = append(ids, id) } if len(ids) > 0 { return ids, rows.Err() } } return []string{profileID}, nil } func (s *Service) profileEmails(ctx context.Context, externalUserID string, profileIDs []string) ([]string, error) { rows, err := s.db.Query(ctx, ` SELECT primary_email FROM contact_discovered_profiles p JOIN users u ON p.user_id = u.id WHERE u.external_id = $1 AND p.id = ANY($2::uuid[]) `, externalUserID, profileIDs) if err != nil { return nil, err } defer rows.Close() seen := map[string]struct{}{} var emails []string for rows.Next() { var email string if err := rows.Scan(&email); err != nil { return nil, err } low := strings.ToLower(strings.TrimSpace(email)) if low == "" { continue } if _, ok := seen[low]; ok { continue } seen[low] = struct{}{} emails = append(emails, low) } return emails, rows.Err() } func (s *Service) persistEmailRejections(ctx context.Context, externalUserID string, emails []string, rejectionType string) { for _, email := range emails { _, _ = s.db.Exec(ctx, ` INSERT INTO contact_discovery_rejections (user_id, rejection_key, rejection_type) VALUES ((SELECT id FROM users WHERE external_id = $1), $2, $3) ON CONFLICT DO NOTHING `, externalUserID, "email:"+email, rejectionType) } } func (s *Service) rejectPendingSuggestions(ctx context.Context, externalUserID string, profileIDs []string) { _, _ = s.db.Exec(ctx, ` UPDATE contact_enrichment_suggestions SET status = 'rejected', rejected_at = NOW() WHERE user_id = (SELECT id FROM users WHERE external_id = $1) AND profile_id = ANY($2::uuid[]) AND status = 'pending' `, externalUserID, profileIDs) } func (s *Service) IgnoreProfile(ctx context.Context, externalUserID, profileID string) ([]string, error) { ids, err := s.relatedProfileIDs(ctx, externalUserID, profileID) if err != nil { return nil, err } _, err = s.db.Exec(ctx, ` UPDATE contact_discovered_profiles SET status = 'ignored', ignored_at = NOW(), updated_at = NOW() WHERE user_id = (SELECT id FROM users WHERE external_id = $1) AND id = ANY($2::uuid[]) `, externalUserID, ids) if err != nil { return nil, err } emails, err := s.profileEmails(ctx, externalUserID, ids) if err != nil { return nil, err } s.persistEmailRejections(ctx, externalUserID, emails, "ignore") s.rejectPendingSuggestions(ctx, externalUserID, ids) return emails, nil } func (s *Service) BlockProfile(ctx context.Context, externalUserID, profileID string) ([]string, error) { ids, err := s.relatedProfileIDs(ctx, externalUserID, profileID) if err != nil { return nil, err } _, err = s.db.Exec(ctx, ` UPDATE contact_discovered_profiles SET status = 'blocked', blocked_at = NOW(), user_blocked = true, is_spam_heavy = true, updated_at = NOW() WHERE user_id = (SELECT id FROM users WHERE external_id = $1) AND id = ANY($2::uuid[]) `, externalUserID, ids) if err != nil { return nil, err } emails, err := s.profileEmails(ctx, externalUserID, ids) if err != nil { return nil, err } s.persistEmailRejections(ctx, externalUserID, emails, "block") s.rejectPendingSuggestions(ctx, externalUserID, ids) return emails, nil } func (s *Service) ListProfilesByStatus(ctx context.Context, externalUserID string, status ProfileStatus) ([]Profile, error) { rows, err := s.db.Query(ctx, ` SELECT `+profileSelectColumns+` FROM contact_discovered_profiles p JOIN users u ON p.user_id = u.id WHERE u.external_id = $1 AND p.status = $2 ORDER BY `+profileInteractionOrderBy+` `, externalUserID, status) if err != nil { return nil, err } defer rows.Close() var profiles []Profile for rows.Next() { p, err := scanProfileRow(rows) if err != nil { return nil, err } profiles = append(profiles, p) } if err := rows.Err(); err != nil { return nil, err } s.attachSignaturesToProfiles(ctx, profiles) return profiles, nil } func (s *Service) ListOtherProfileGroups(ctx context.Context, externalUserID string) ([]ProfileGroup, error) { page, err := s.ListOtherProfileGroupsPage(ctx, externalUserID, MaxOtherGroupsPageSize, 0, "") if err != nil { return nil, err } if page.Total <= page.Limit { return page.Groups, nil } all := append([]ProfileGroup{}, page.Groups...) for offset := page.Limit; offset < page.Total; offset += MaxOtherGroupsPageSize { next, err := s.ListOtherProfileGroupsPage(ctx, externalUserID, MaxOtherGroupsPageSize, offset, "") if err != nil { return nil, err } all = append(all, next.Groups...) } return all, nil }