- 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.
170 lines
4.4 KiB
Go
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
|
|
}
|