Contacts
π Overviewβ
internal/api/v1/crm/services/contact.service.js is the largest CRM surface. It manages people and business records, enforces owner/follower visibility preferences, maps custom fields, handles attachments and exports, and orchestrates background jobs for bulk linking or imports. Controllers mostly validate payloads; this service performs the real liftingβMongo aggregations, Stripe-style pagination, Wasabi cleanup, queue insertion, and CRM activity fan-out.
ποΈ Collections Usedβ
π Full Schema: See Database Collections Documentation
crm.contactsβ
- Operations: Heavy read/write (aggregation, create/update/delete, bulk operations)
- Model:
shared/models/contact.js - Usage Context: Stores person/business records, embedded custom fields, attachments, associations
_accountsβ
- Operations: Lookups to resolve sub-account status (aggregations in list endpoints)
- Model:
shared/models/account.js - Usage Context: Adds account metadata during list queries and exports
crm.notesβ
- Operations: Create during bulk note association
- Model:
shared/models/crm-note.js - Usage Context: Records note content when
putAssociateNotesContactruns
crm.activityβ
- Operations: Inserts via
addActivityutility when notes are attached in bulk - Model:
shared/models/activity.js - Usage Context: Audit trail for contact interactions
queuesβ
- Operations: Inserts linking jobs (
putAssociateContact) and CSV processing tasks - Model:
shared/models/queues.js - Usage Context: Asynchronous contact linking and post-checkout workflows
Supporting Collectionsβ
crm-tag,crm.pipeline.stages,crm.deals,crm.import.results,crm.attachments(via Wasabi metadata)- External integration tokens (
hubspot-key,salesforce-key, etc.) resolved bygetConnectedContact
π Data Flowβ
flowchart TD
A[Request] --> B{Owner visibility?}
B -->|no| C[Build filters from search/filter payload]
B -->|restricted| D[Append owner/follower clause]
C --> E[Aggregate contacts]
D --> E
E --> F[setupContactPromises]
F --> G[expandContacts (currency, totals, associations)]
G --> H[Response]
subgraph Mutations
I[Create/Update Payload] --> J[validateTags + validateEmailOrMobile]
J --> K[Verify associations]
K --> L[Contact.save/update]
L --> M[Assignment helpers + expandContacts]
M --> H
end
subgraph Attachments
N[Add/Delete attachment] --> O[Wasabi upload/delete]
O --> P[Update contact attachments]
P --> H
end
subgraph Bulk Ops
Q[Mass selection/all switch] --> R[Contact.find/count]
R --> S[Queue job or apply loop]
S --> H
end
π§ Business Logic & Functionsβ
Listing & Searchβ
getContact({ account_id, is_owner, uid, page, limit, search, filter, filters, accountCurrency, auth })β
- Purpose: Master list endpoint combining search, advanced filters, and pagination.
- Highlights:
- Builds regex search across core fields and nested
additional_infoentries. - Enforces owner visibility preferences (
owner_followers) when the requester is not the owner. - Aggregates
_accountsto appendsub_account_statusbefore filtering. - Expands contacts via
setupContactPromises+expandContactsto hydrate computed properties (balances, linked entities, currency formatting).
- Builds regex search across core fields and nested
- Errors: Returns
{ success:false, errno:400 }with the thrown message when queries or expansions fail.
getBusinessesContact / getPeopleContactβ
- Purpose: Type-specific list endpoints for businesses and people.
- Highlights:
- Support origin, sorting, association toggles, and populate flags.
- Normalise associated records (linked contacts) and optionally limit expansions.
- Apply additional filters such as
associatedboolean,associated_limit, and custom sorts (including stage-based ordering for people linked to deals).
- Side Effects: Noneβpure read.
postFilterContactβ
- Purpose: JSON-filtered search using the builder in
utilities.generateMongoDbQuery. - Highlights: Accepts advanced logical expressions, respects visibility rules, and shares the same expansion pipeline as general lists.
postSearchContactβ
- Purpose: Global search across contacts (people/business/both) with optional type filtering.
- Highlights: Normalises search text, dedupes results, and respects
typeparameter semantics.
getConnectedContactβ
- Purpose: Report which third-party CRMs (HubSpot, Salesforce, Keap, Zoho, Pipedrive, Constant Contact, Mailchimp, ActiveCampaign) are connected for a given owner.
- Highlights: Fetches token documents per provider and returns
connected/token_idflags.
getBusinessContact, getPersonContact, getBusinessAssociatedContactβ
- Purpose: Detailed views with optional expansions for attachments, associated records, tags, deals, and currencies.
- Highlights: Use
setupBusinessContactPromises/getBusinessAssociatedContactsutilities to hydrate relationships and guard access by visibility rules.
exportBusinessesContact, exportPeopleContactβ
- Purpose: CSV export using
json2csvand Wasabi for storage when required. - Highlights: Apply same filtering logic as list endpoints, stream formatted data (including expanded custom fields) for download.
getTotalNewContactβ
- Purpose: Return counts per contact type within a date window.
- Highlights: Aggregates by
typeafter applying owner visibility restrictions.
Creation & Updatesβ
postBusinessContact, postPersonContactβ
- Purpose: Create business or person contacts.
- Highlights:
- Validate tags via
validateTagsand enforce unique email/phone throughvalidateEmailOrMobile. - Resolve relationships (
businessAndPersonAssignment,verifyContactIds) before persisting. - Store owner defaults (
ownerfallback to creator) and capture metadata likelast_activity,last_contacted. - After save, run expansion pipeline to return enriched payload.
- Validate tags via
putBusinessContact, putPersonContactβ
- Purpose: Update existing contacts.
- Highlights:
- Guard against editing locked contacts (limited to follower assignments, deals, etc.).
- When owner changes, adjust follower lists to avoid duplicates.
- Resynchronise relationships (business β person) and map custom fields via
mapFields. - Re-expand response to mirror creation output.
putAttachmentsContactβ
- Purpose: Append attachments to contacts.
- Highlights: Accepts metadata payloads, pushes attachments to embedded array, ensures Wasabi keys are preserved for later deletions.
deleteAttachmentContactβ
- Purpose: Remove a specific attachment.
- Highlights: Deletes Wasabi object via
Wasabi.deleteByKeythen updates contact document.
deleteBusinessContact, deletePersonContactβ
- Purpose: Remove single contacts (soft delete semantics defined by controller).
- Highlights: Validate ownership/visibility and clean up associations when necessary.
deletePeopleContact, deleteBusinessesContactβ
- Purpose: Bulk deletions using selected IDs or the
allswitch. - Highlights: Validate contact counts before deletion, optionally pull IDs by re-running filters when
allis true.
Bulk Associations & Notesβ
putAssociateContactβ
- Purpose: Assign owners/followers in bulk.
- Highlights: Validates visibility scope, supports
allvs specific IDs, and queues background linking jobs for cross-contact association when requested.
putAssociateTagContactβ
- Purpose: Add/remove tags in bulk.
- Highlights: Validates tag IDs via
validateTags, applies add/remove semantics, and ensures duplicates are prevented.
putAssociateNotesContactβ
- Purpose: Attach the same note (and optional attachments) to multiple contacts.
- Highlights: Creates
CRMNoteentries per contact, pushes attachments, triggersaddActivityfor each note, and respects visibility filters.
Filtering, Linking & Utilitiesβ
linkCustomersβ
- Purpose: Persist Stripe mapping details for contact bulk linking flows.
- Highlights: Normalises mapping keys (replace dots with dashes), updates
StripeToken, and optionally enqueues aqueuesjob to process linking asynchronously.
removeContactFromDeals, purgeInstasitesAndReports, removeAssociatedAccountsβ
- Purpose: Helper utilities to clean dependent resources when contacts are deleted.
purge helpers & deleteAdditionalDataβ
- Purpose: Ensure no orphaned attachments, reports, or associated data remain after deletion.
Attachments & Mediaβ
- Uses
Wasabiutility for upload and deletion whenever attachments are added or removed. - Attachment payloads normalised to include
id,key,location, andcontent_type. - Cleans up Wasabi objects during contact deletion or attachment removal loops.
Background Jobs & Exportsβ
Queuejobs created for bulk linking (linkCustomers) and for contact note association when executed asynchronously.exportPeopleContact/exportBusinessesContactleveragejson2csvand may stream results directly or via Wasabi, depending on controller context.
π Integration Pointsβ
Internal Dependenciesβ
../utils/contactβ utilities for mapping, expansion, tag validation, timezone lookup.shared/utilities/filterβ builds MongoDB queries from filter payloads.shared/utilities/wasabiβ attachment storage management.shared/utilities/activityβ creates CRM activity records.shared/utilities/catch-errorsβ consistent error envelopes (custom,notAllowed,conflict).
External Servicesβ
- Wasabi S3 β stores attachments and exported CSV assets.
- Conversation Socket β authenticated POST to
${CONVERSATION_SOCKET}/emitwhen notifications must be broadcast (during Stripe mapping updates).
π§ͺ Edge Cases & Special Handlingβ
- Visibility rules: Every list/update path enforces owner/follower preferences when
is_owneris false. - Duplicate prevention:
validateEmailOrMobileand fingerprint checks stop duplicates on creation. - Bulk
alloperations: Safe guards ensure the contact count matches before applying updates/deletes. - Attachment cleanup: Wasabi keys are always removed before array updates to avoid orphaned files.
- Cross-link verification:
verifyContactIdsblocks associating inaccessible contacts across types.
β οΈ Important Notesβ
- π¨ Do not edit service-level models/utilities β rely on
/sharedcopies. - π‘ Always rerun
setupContactPromisesafter mutating contacts to guarantee parity with UI expectations. - π CSV filter errors: malformed filter payloads return
Query Validation Error; controllers should surface error to user intact.