Skip to main content

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
  • _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

πŸ”§ 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:

  1. Account Preload
    • Queries _accounts for the parent account plus any sub-accounts referencing it.
    • Normalizes the result into a plain array of ObjectIds.
  2. User Filtering
    • Finds active _users whose account is in the preloaded set and whose email matches (case-insensitive).
    • Populates each user’s account.business to obtain display names.
  3. Dashboard Preference Check
    • For every matched user, fetches a single ProjectsDashboardPreferences document.
    • Applies two business skips:
      • Skip when client dashboard is disabled but user is marked service=true and software=false.
      • Skip when client dashboard is allowed but both service and software flags are false.
  4. 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.findOne is 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:

  1. Uses an aggregation pipeline to match active users by email.
  2. Joins _accounts into the pipeline and keeps only accounts with a parent_account.
  3. Deconstructs the account array to a single object.
  4. Looks up crm.contacts to derive the business name.
  5. 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 on account and business exist.

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:

  1. Fetches parent and sub-account IDs identical to find.
  2. Queries _users by phone across those accounts.
  3. Populates account.business to extract names.
  4. 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:

  1. Aggregates active _users by email.
  2. Looks up associated _accounts and filters to account.main === true.
  3. Unwinds the account and joins crm.contacts to fetch the business name.
  4. Projects a lean account object containing _id and name.

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.
πŸ’¬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM