## Account branding enrichment during signup This PR does the following ### Replace Firecrawl with Context.dev Switches the enterprise brand lookup from Firecrawl to Context.dev for better data quality, built-in caching, and automatic filtering of free/disposable email providers. The service interface changes from URL to email input to match Context.dev's email endpoint. OSS still falls back to basic HTML scraping with a normalized output shape across both paths. The enterprise path intentionally does not fall back to HTML scraping on failure — speed matters more than completeness. We want the user on the editable onboarding form fast, and a slow fallback scrape is worse than letting them fill it in. Requires `CONTEXT_DEV_API_KEY` in Super Admin → App Config. Without it, falls back to OSS HTML scraping. ### Add job to enrich account details After account creation, `Account::BrandingEnrichmentJob` looks up the signup email and pre-fills the account name, colors, logos, social links, and industry into `custom_attributes['brand_info']`. The job signals completion via a short-lived Redis key (30s TTL) + an ActionCable broadcast (`account.enrichment_completed`). The Redis key lets the frontend distinguish "still running" from "finished with no results."
66 lines
1.7 KiB
Ruby
66 lines
1.7 KiB
Ruby
module SocialLinkParser
|
|
extend ActiveSupport::Concern
|
|
|
|
SOCIAL_DOMAIN_MAP = {
|
|
whatsapp: %w[wa.me api.whatsapp.com],
|
|
line: %w[line.me],
|
|
facebook: %w[facebook.com fb.com fb.me],
|
|
instagram: %w[instagram.com],
|
|
telegram: %w[t.me telegram.me],
|
|
tiktok: %w[tiktok.com]
|
|
}.freeze
|
|
|
|
private
|
|
|
|
def extract_social_from_links(links)
|
|
handles = {}
|
|
SOCIAL_DOMAIN_MAP.each do |platform, domains|
|
|
handles[platform] = find_social_handle(links, platform, domains)
|
|
end
|
|
handles
|
|
end
|
|
|
|
def find_social_handle(links, platform, domains)
|
|
matching_links = links.select do |l|
|
|
uri = URI.parse(l)
|
|
domains.any? { |d| match_social_domain?(uri.host, d) }
|
|
rescue URI::InvalidURIError
|
|
false
|
|
end
|
|
|
|
matching_links.each do |link|
|
|
handle = parse_social_handle(platform, link)
|
|
return handle if handle.present?
|
|
end
|
|
nil
|
|
end
|
|
|
|
def match_social_domain?(host, domain)
|
|
return false if host.blank?
|
|
|
|
host == domain || host.end_with?(".#{domain}")
|
|
end
|
|
|
|
SHARE_PATH_PREFIXES = %w[sharer share intent dialog].freeze
|
|
|
|
def parse_social_handle(platform, link)
|
|
uri = URI.parse(link)
|
|
return extract_whatsapp_phone(uri) if platform == :whatsapp
|
|
|
|
handle = uri.path.to_s.delete_prefix('/').delete_suffix('/')
|
|
return nil if handle.blank?
|
|
return nil if SHARE_PATH_PREFIXES.any? { |prefix| handle.start_with?(prefix) }
|
|
|
|
handle.presence
|
|
rescue URI::InvalidURIError
|
|
nil
|
|
end
|
|
|
|
# wa.me/1234567890 or api.whatsapp.com/send?phone=1234567890
|
|
def extract_whatsapp_phone(uri)
|
|
phone = CGI.parse(uri.query.to_s)['phone']&.first
|
|
phone = uri.path.to_s.delete_prefix('/').delete_suffix('/') if phone.blank?
|
|
phone.presence&.gsub(/[^\d]/, '')
|
|
end
|
|
end
|