Account
π Overviewβ
The account service consolidates account discovery operations used by login assistants and support tooling. It returns the set of accounts a user can access, filters sub-accounts by dashboard permissions, and exposes partner shortcuts that power SSO selection screens. Every method here resolves data from _accounts, _users, and _projects.dashboard.preferences with tightly scoped projections so client dashboards stay responsive.
File Path: internal/api/v1/auth/services/account.service.js
π Data Flowβ
flowchart TD
A[π₯ Request] --> B{Selector Type}
B -->|Email| C[Fetch parent & child accounts]
B -->|Phone| D[Fetch parent & child accounts]
C --> E[Match active users]
D --> E
E --> F[Populate account.business]
F --> G[Fetch dashboard preferences]
G --> H{Dashboard Rules}
H -->|β
Pass| I[π€ Return account payload]
H -->|β Skip| E
πΎ Database Operationsβ
Collections Usedβ
_accountsβ Loads parent accounts and their sub-accounts used to scope user queries.- Operations: Read, Aggregation
$lookup - Model:
shared/models/account.js
- Operations: Read, Aggregation
_usersβ Fetches active users by email or phone and populates business context.- Operations: Read, Aggregation, Populate
- Model:
shared/models/user.js
_projects.dashboard.preferencesβ Determines whether client dashboards should be exposed.- Operations: Read
- Model:
shared/models/projects-dashboard-preferences.js
crm.contactsβ Used in aggregation pipelines to rehydrate business names.- Operations:
$lookup - Model:
shared/models/crm/contact.js
- Operations:
π§ Business Logic & Functionsβ
Core Functionsβ
find(email, parent_account)β
Purpose: Returns all accounts (parent and sub-accounts) where a user with the provided email is active, filtered by dashboard access metadata.
Parameters:
email(String) β Email address supplied by the login assistant.parent_account(String|ObjectId) β Parent account context that scopes the search.
Returns: Promise<Array<AccountSummary>>
AccountSummary.idβ Account identifier.AccountSummary.nameβ Business name pulled from the accountβs business reference.AccountSummary.allow_client_dashboardβ Boolean flag from dashboard preferences.AccountSummary.is_ownerβ Indicates whether the user is the account owner.AccountSummary.metadataβ Service/software access toggles from the user record.
Business Logic Flow:
- Account Preload
- Queries
_accountsfor the parent account plus any sub-accounts referencing it. - Normalizes the result into a plain array of ObjectIds.
- Queries
- User Filtering
- Finds active
_userswhoseaccountis in the preloaded set and whoseemailmatches (case-insensitive). - Populates each userβs
account.businessto obtain display names.
- Finds active
- Dashboard Preference Check
- For every matched user, fetches a single
ProjectsDashboardPreferencesdocument. - Applies two business skips:
- Skip when client dashboard is disabled but user is marked
service=trueandsoftware=false. - Skip when client dashboard is allowed but both
serviceandsoftwareflags arefalse.
- Skip when client dashboard is disabled but user is marked
- For every matched user, fetches a single
- Response Preparation
- Maps surviving rows into the compact summary payload returned to the controller.
Error Handling:
- Returns an empty array when no users match; no errors are thrown for missing dashboard preferences.
Dependencies:
ProjectsDashboardPreferences.findOneis invoked once per matched user, so expect N+1 database patterns for large account lists.
Example Usage:
const accounts = await accountService.find('agent@example.com', req.query.account);
// [{ id: '64fβ¦', name: 'Acme Widgets', allow_client_dashboard: true, β¦ }]
Side Effects:
- None; all operations are reads.
findSubAcc(email)β
Purpose: Produces a lightweight list of sub-accounts where the user is active.
Parameters:
email(String) β Email filter applied in lowercase.
Returns: Promise<Array<{id: ObjectId, name: String}>>
Business Logic Flow:
- Uses an aggregation pipeline to match active users by email.
- Joins
_accountsinto the pipeline and keeps only accounts with aparent_account. - Deconstructs the account array to a single object.
- Looks up
crm.contactsto derive the business name. - Projects only the account ID and name.
Error Handling:
- Pipeline simply yields
[]when nothing matches; no exceptions are thrown.
Dependencies:
- Heavy reliance on MongoDB
$lookup; ensure indexes onaccountandbusinessexist.
Example Usage:
const subAccounts = await accountService.findSubAcc('client@example.com');
// [{ id: '650β¦', name: 'Client Sub Account' }]
findPhone(phone, parent_account)β
Purpose: Mirrors find but pivots on phone number instead of email.
Parameters:
phone(String) β Phone number as stored in_users.phone.parent_account(String|ObjectId) β Parent account boundary.
Returns: Promise<Array<AccountSummary>>
Business Logic Flow:
- Fetches parent and sub-account IDs identical to
find. - Queries
_usersbyphoneacross those accounts. - Populates
account.businessto extract names. - Fetches dashboard preferences for each match and builds the summary payload.
Differences From find:
- Skipping rules do not short-circuit results; all matches are returned regardless of preference combos.
Example Usage:
const accounts = await accountService.findPhone('+19541234567', req.query.account);
findPartner(email)β
Purpose: Surfaces main accounts tied to a partner user, enabling partner SSO flows.
Parameters:
email(String) β Partner email address.
Returns: Promise<Array<{_id: ObjectId, name: String}>>
Business Logic Flow:
- Aggregates active
_usersby email. - Looks up associated
_accountsand filters toaccount.main === true. - Unwinds the account and joins
crm.contactsto fetch the business name. - Projects a lean
accountobject containing_idandname.
Example Usage:
const partners = await accountService.findPartner('partner@agency.com');
// [{ _id: '64eβ¦', name: ['Partner Agency'] }]
π Integration Pointsβ
Internal Dependenciesβ
internal/api/v1/auth/controllers/account.controller.jsβ Each HTTP handler delegates directly to the corresponding service method.shared/utilities/loggerβ Only referenced indirectly; this service itself does not log.
External Servicesβ
- None. All data is sourced from MongoDB collections.
π¨ Data Transformation Pipelineβ
graph LR
A[π₯ Raw User Inputs] --> B[Lowercase email / sanitize phone]
B --> C[Fetch parent & child accounts]
C --> D[Retrieve active users]
D --> E[Populate business context]
E --> F[Join dashboard preferences]
F --> G[Apply business rules]
G --> H[π€ Final account summaries]
π§ͺ Edge Cases & Special Handlingβ
Case 1: Client dashboard suppressionβ
Condition: User metadata indicates a service-only seat and dashboard preferences disallow the client dashboard. Handling: Entry is skipped entirely, preventing the UI from showing inaccessible dashboards.
Case 2: Missing dashboard preference documentβ
Condition: No ProjectsDashboardPreferences document exists for the user/account pair.
Handling: Preference lookups return null; the payload defaults to undefined for allow_client_dashboard and the record is included.
Case 3: Partner lookup without main flagβ
Condition: User belongs to accounts that are not marked main.
Handling: Aggregation stage rejects the record, resulting in an empty response.
β οΈ Important Notesβ
- π Security: The service never reveals user emails or phone numbers in the response; only account metadata is returned.
- π‘ Best Practice: Callers should debounce input in the UI because each keystroke triggers a multi-query operation.
- π¨ Performance: The N+1 preference lookup can be expensive for users tied to many accounts. Consider batching or caching when used in high-traffic flows.