ultisuite-backend/internal/contacts/discovery/suggestable.go
R3D347HR4Y 556d5f416d Enhance API and configuration for contact discovery and public sharing
- Introduced new endpoints for contact discovery, including scanning, listing, and managing discovered contacts.
- Implemented retry logic for handling missing DAV credentials during contact operations.
- Added public share functionality for drive API, allowing users to manage public shares, including upload, delete, and rename operations.
- Updated Nextcloud configuration to support public share links and improved error handling for public share permissions.
- Enhanced logging and validation across contact and drive APIs for better error tracking and user feedback.
- Added tests for new contact matching and ranking functionalities to ensure accuracy and reliability.
2026-06-06 20:27:02 +02:00

170 lines
4.4 KiB
Go

package discovery
import (
"strings"
)
// suggestableProfilesSQL filters profiles that only have a bare email address.
const suggestableProfilesSQL = `
AND (
EXISTS (
SELECT 1 FROM contact_discovered_signatures s
WHERE s.profile_id = p.id
)
OR (
COALESCE(trim(p.display_name), '') != ''
AND p.display_name NOT ILIKE '%@%'
AND lower(trim(p.display_name)) != lower(split_part(p.primary_email, '@', 1))
)
OR (
p.enriched_data IS NOT NULL
AND (
nullif(trim(p.enriched_data->>'first_name'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'last_name'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'company'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'department'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'job_title'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'website'), '') IS NOT NULL
OR nullif(trim(p.enriched_data->>'notes'), '') IS NOT NULL
OR jsonb_array_length(COALESCE(p.enriched_data->'social_profiles', '[]'::jsonb)) > 0
OR jsonb_array_length(COALESCE(p.enriched_data->'phones', '[]'::jsonb)) > 0
OR jsonb_array_length(COALESCE(p.enriched_data->'addresses', '[]'::jsonb)) > 0
)
)
OR EXISTS (
SELECT 1
FROM jsonb_array_elements(COALESCE(p.all_emails, '[]'::jsonb)) AS e
WHERE nullif(trim(e->>'display_name'), '') IS NOT NULL
AND (e->>'display_name') NOT ILIKE '%@%'
AND lower(trim(e->>'display_name')) != lower(split_part(COALESCE(e->>'email', ''), '@', 1))
)
)`
func hasMeaningfulDisplayName(name, email string) bool {
name = strings.TrimSpace(name)
if name == "" || strings.Contains(name, "@") {
return false
}
local := emailLocalPart(email)
if local == "" {
return true
}
return strings.ToLower(name) != local
}
func emailLocalPart(email string) string {
email = strings.TrimSpace(email)
at := strings.LastIndex(email, "@")
if at <= 0 {
return strings.ToLower(email)
}
return strings.ToLower(email[:at])
}
func enrichedDataHasValueBeyondEmail(data *EnrichedContactData) bool {
if data == nil {
return false
}
if strings.TrimSpace(data.FirstName) != "" ||
strings.TrimSpace(data.LastName) != "" ||
strings.TrimSpace(data.Company) != "" ||
strings.TrimSpace(data.Department) != "" ||
strings.TrimSpace(data.JobTitle) != "" ||
strings.TrimSpace(data.Website) != "" ||
strings.TrimSpace(data.Notes) != "" {
return true
}
for _, sp := range data.SocialProfiles {
if strings.TrimSpace(sp.Value) != "" {
return true
}
}
for _, p := range data.Phones {
if strings.TrimSpace(p.Value) != "" {
return true
}
}
for _, a := range data.Addresses {
if strings.TrimSpace(a.Street) != "" ||
strings.TrimSpace(a.City) != "" ||
strings.TrimSpace(a.Region) != "" ||
strings.TrimSpace(a.PostalCode) != "" ||
strings.TrimSpace(a.Country) != "" {
return true
}
}
return false
}
// ProfileHasValueBeyondEmail reports whether a profile has contact info beyond a bare email.
func ProfileHasValueBeyondEmail(p Profile) bool {
if len(p.Signatures) > 0 {
return true
}
if enrichedDataHasValueBeyondEmail(p.EnrichedData) {
return true
}
if hasMeaningfulDisplayName(p.DisplayName, p.PrimaryEmail) {
return true
}
for _, e := range p.AllEmails {
if hasMeaningfulDisplayName(e.DisplayName, e.Email) {
return true
}
}
return false
}
func profileGroupHasValueBeyondEmail(group ProfileGroup) bool {
for _, p := range group.Profiles {
if ProfileHasValueBeyondEmail(p) {
return true
}
}
return ProfileHasValueBeyondEmail(group.Profile)
}
func profileHasNoReplyEmail(p Profile) bool {
if isNoReplyEmail(p.PrimaryEmail) {
return true
}
for _, e := range p.AllEmails {
if isNoReplyEmail(e.Email) {
return true
}
}
if p.EnrichedData != nil {
for _, e := range p.EnrichedData.Emails {
if isNoReplyEmail(e.Value) {
return true
}
}
}
return false
}
func profileGroupHasNoReplyEmail(group ProfileGroup) bool {
for _, p := range group.Profiles {
if profileHasNoReplyEmail(p) {
return true
}
}
return profileHasNoReplyEmail(group.Profile)
}
func filterSuggestableGroups(groups []ProfileGroup) []ProfileGroup {
if len(groups) == 0 {
return groups
}
out := make([]ProfileGroup, 0, len(groups))
for _, g := range groups {
if profileGroupHasNoReplyEmail(g) {
continue
}
if profileGroupHasValueBeyondEmail(g) {
out = append(out, g)
}
}
return out
}