Skip to main content

User Configuration Management

๐Ÿ“– Overviewโ€‹

internal/api/v1/users/services/config.js handles personalization and UI configuration for users, including customizable field visibility, card layouts, dashboard widgets, app drawer menu items, and admin notification preferences. Supports 25+ configuration types across contacts, deals, forms, analytics, and more.

File Path: internal/api/v1/users/services/config.js
Lines of Code: 1,245

๐Ÿ—„๏ธ Collections Usedโ€‹

๐Ÿ“š Full Schema: See Database Collections Documentation

user_configsโ€‹

  • Operations: Full CRUD for user-specific UI configurations
  • Model: shared/models/user-config.js
  • Usage Context: Stores per-user, per-account, per-type configuration (field visibility, card layout, preferences)

configsโ€‹

  • Operations: Read for system-wide default configurations
  • Model: shared/models/config.js
  • Usage Context: Default field configurations, indoc steps, system templates

accountsโ€‹

  • Operations: Update for admin-only settings (notifications, SSO, conversation preferences)
  • Model: shared/models/account.js
  • Usage Context: Account-level preferences that override user settings

๐Ÿ”„ Data Flowโ€‹

sequenceDiagram
participant User
participant Controller
participant Service
participant Database

User->>Controller: GET /config/:type
Controller->>Service: getConfig(type)
Service->>Database: Find UserConfig

alt Config exists
Service->>Database: Return config
else No config
Service->>Database: Get defaults
Service->>Database: Create new config
end

Service->>Service: Add mandatory fields
Service->>Service: Add visible fields
Service-->>Controller: Config with metadata
Controller-->>User: Configuration object

User->>Controller: PATCH /config/:type
Controller->>Service: updateConfig(type, data)
Service->>Database: Get defaults
Service->>Service: Validate config
Service->>Database: Update UserConfig
Service->>Database: Update Account (if admin settings)
Service-->>Controller: Updated config

๐Ÿงฉ Configuration Typesโ€‹

Supported Types (25+):

CRM & Sales:

  • contacts - Contact field visibility, card layout
  • contacts_instasite - Contact fields for instasite creation
  • contacts_instareport - Contact fields for instareport creation
  • deals - Deal field visibility, card layout
  • forms - Form configuration
  • templates - Email template settings

Marketing & Analytics:

  • inbound - Inbound campaign configuration
  • callrail - CallRail integration display
  • calltrackingmetrics - Call Tracking Metrics display
  • google_ads_campaign, google_ads_adgroup, google_ads_ad, google_ads_keywords - Google Ads configurations
  • facebook_ads_campaigns, facebook_ads_adsets, facebook_ads_ads - Facebook Ads configurations
  • tiktok_campaign, tiktok_adgroup, tiktok_ads - TikTok Ads configurations

Products & Services:

  • instasites - Instasite field configuration
  • edocs - E-docs field configuration
  • leadfinder - Lead Finder display settings

System:

  • dashboard - Dashboard widget configuration
  • dashboard-menu - App drawer menu items
  • sso - Single sign-on configuration
  • conversations - Conversation/chat settings

๐Ÿ”ง Business Logic & Functionsโ€‹

Configuration Retrievalโ€‹


getConfig({ type, uid, account_id })โ€‹

Purpose: Retrieves or creates user configuration for a specific type with default mandatory and visible fields injected.

Parameters:

  • type (String) - Configuration type (e.g., 'contacts', 'deals', 'dashboard')
  • uid (ObjectId) - User ID
  • account_id (ObjectId) - Account ID

Returns: Promise<Object> - Configuration object with:

  • _id, user_id, account_id, type
  • fields (Object) - Field visibility/order per entity type
  • card (Object) - Card layout configuration (left, right, centre)
  • preferences (Object) - Type-specific preferences

Business Logic Flow:

  1. Find Existing Config

    • Query: { user_id: uid, account_id, type }
  2. Create Default If Missing

    • Fetch system defaults from configs collection (type: 'default-user-config-fields')
    • Create new UserConfig with defaults for type
  3. Inject Mandatory Fields

    • Call getMandatoryFieldsConditions(type, config)
    • Adds required field validators (e.g., contacts require name OR email)
  4. Inject Visible Fields

    • Call getVisibleFieldsOnAdd(type, config)
    • Adds fields visible in "add new" dialogs
  5. Return Enhanced Config

Key Business Rules:

  • Lazy creation: Config created on first access
  • Defaults pulled from system config collection
  • Mandatory fields enforced at UI level
  • Visible fields control "add new" dialog

Example Usage:

const config = await configService.getConfig({
type: 'contacts',
uid: req.user._id,
account_id: req.account_id,
});
// Returns: { fields: { businesses: [...], people: [...] }, card: {...}, preferences: {...} }

getAllConfigs({ uid, account_id, account, user })โ€‹

Purpose: Retrieves all configuration types for a user in a single response. Creates missing configs automatically.

Parameters:

  • uid, account_id, account, user - Standard context

Returns: Promise<Object> - Aggregated configurations:

  • user_id, account_id
  • configurations (Array) - All config types with:
    • Standard configs (contacts, deals, etc.)
    • admin_notifications - Account-level notification settings
    • preferences - Account-level preferences
    • indoc-steps - Onboarding steps (main accounts only)

Business Logic Flow:

  1. Fetch All User Configs

    • Query: { user_id, account_id }
  2. Identify Missing Types

    • Compare found types against allowedTypes array
    • Create missing configs with defaults
  3. Special Configs:

    • dashboard: Use getDefaultDashboardWidgets(account.main)
    • dashboard-menu: Use setDefaultDashboardMenu(account.main, user.dashclicks?.general)
  4. Verify Dashboard Menu

    • Call verifyDashboardMenu() to ensure menu has required items
    • Update if missing/extra items found
  5. Ensure Conversation Preferences

    • If account lacks preferences.conversations, inject defaults:
      • display_team_routing, live_chat_code, live_chat_bubble, availability, required_information
    • If missing welcome_message, add defaults
  6. Enhance Each Config

    • Apply mandatory fields
    • Apply visible fields
    • Skip dashboard and dashboard-menu (already have correct structure)
  7. Append Additional Data

    • Add admin_notifications from account
    • Add preferences from account
    • Add indoc-steps (if main account)
  8. Return Aggregated

Key Business Rules:

  • Lazy creation of all missing configs
  • Dashboard widgets differ for main vs sub-accounts
  • Menu items differ based on account type and user role
  • Conversation preferences have extensive defaults
  • Main accounts get onboarding steps

Configuration Updateโ€‹


updateConfig({ type, data, FSCache, user, account_id, uid })โ€‹

Purpose: Updates user configuration with validation, permission checks, and account-level preference handling.

Parameters:

  • type (String) - Config type
  • data (Object) - Configuration changes:
    • fields (Object) - Field visibility/order updates
    • card (Object) - Card layout updates
    • preferences (Object) - Preference updates
  • user (Object) - Requesting user (for permission checks)
  • Other params standard

Returns: Promise<{ config: Object, additional_data: Object }> - Updated config + account changes

Business Logic Flow:

  1. Fetch System Defaults

    • Get default-user-config-fields from configs collection
  2. Validate Configuration

    • Call validateConfig(defaults[type], data, type)
    • Merges user changes with system defaults
    • Handles field visibility, card layout, preferences
  3. Permission Checks (restrictive settings):

    • forms.admin_notification: Requires user.is_owner
    • instareports.admin_notification: Requires owner
    • instasites.admin_notification: Requires owner
    • sso: Requires owner
    • conversations.admin: Requires owner
    • contacts.visibility: Requires owner
    • deals.visibility: Requires owner
    • Throws forbidden if violated
  4. Extract Account-Level Settings

    • Pull out admin_notification preferences from:
      • preferences.instasites.admin_notification
      • preferences.instareports.admin_notification
      • preferences.forms.admin_notification
    • Remove from user preferences (move to account)
  5. Build Update Queries

    • User Config Updates (dot notation):
      • card.* updates
      • fields.* updates
      • preferences.* updates
    • Account Updates:
      • admin_notifications.* updates
      • preferences.sso.* updates (if SSO changes)
      • preferences.conversations.admin.* updates
      • preferences.contacts.visibility, preferences.deals.visibility
  6. Update Account Document

    • Apply account-level changes
  7. Update User Config Document

    • Apply user-level changes (upsert if not exists)
  8. Return Results

    • config: Updated user config with mandatory/visible fields
    • additional_data: Account changes (type: 'admin_notifications' or 'preferences')

Validation Algorithm (validateConfig):

function validateConfig(defaults, userConfig, type) {
let finalConfig = defaults || {};

// Card Config Validation
if (userConfig?.card) {
// Merge right panel cards (special handling for conversations)
// Merge left panel cards
// Merge centre panel cards
}

// Fields Config Validation
if (userConfig?.fields) {
Object.keys(finalConfig.fields).forEach(fieldType => {
userConfig.fields[fieldType].forEach(row => {
// Update existing field visibility/order
// Or add custom field
});
});
}

// Preferences (pass-through)
if (userConfig?.preferences) {
finalConfig.preferences = userConfig.preferences;
}

return finalConfig;
}

Key Business Rules:

  • Admin notifications stored at account level
  • SSO and conversation admin settings at account level
  • Visibility settings at account level (applies to all users)
  • User-specific settings at user config level
  • Empty objects handled with recursive key-value extraction
  • Upsert ensures config always exists after update

Error Handling:

  • forbidden(403): User lacks permission for admin-only setting

Side Effects:

  • โš ๏ธ Updates UserConfig document (or creates if missing)
  • โš ๏ธ Updates Account document for admin settings

Dashboard & Menu Managementโ€‹


getDashboard({ uid, account_id, account })โ€‹

Purpose: Retrieves user's dashboard widget configuration with default widgets if not configured.

Parameters: Standard uid, account_id, account

Returns: Promise<Object> - Dashboard config:

  • type: 'dashboard'
  • config (Array) - Widget configurations:
    [
    { type: 'analytics', position: { x: 0, y: 0, w: 6, h: 4 }, visible: true },
    { type: 'deals', position: { x: 6, y: 0, w: 6, h: 4 }, visible: true },
    ...
    ]

Business Logic Flow:

  1. Find Existing Config

    • Query: { user_id: uid, account_id, type: 'dashboard' }
  2. Create Default If Missing

    • Use getDefaultDashboardWidgets(account.main)
    • Main accounts get all widgets
    • Sub-accounts exclude: instareports, instasites, affiliate
  3. Return Config

Default Widgets (DASHBOARD_DEFAULTS_WIDGETS):

  • analytics, deals, contacts, conversations, forms, tasks, instareports (main only), instasites (main only), affiliate (main only), projects, google_ads, facebook_ads

updateDashboard({ data, account_id, uid, account })โ€‹

Purpose: Updates dashboard widget layout with validation against allowed types.

Parameters:

  • data (Array) - Widget configurations with types and positions

Returns: Promise<Object> - Updated dashboard config

Business Logic Flow:

  1. Validate Widget Types

    • Extract types from data
    • Get default allowed types for account
    • Calculate diff (types not in defaults)
    • Throw forbidden if unauthorized types
  2. Update Config

    • Upsert: { user_id: uid, account_id, type: 'dashboard' }
    • Set config: data

Key Business Rules:

  • Cannot add widget types not in default list
  • Sub-accounts cannot add main-only widgets

getDashboardMenu({ uid, account_id, account, user })โ€‹

Purpose: Retrieves app drawer menu configuration with automatic missing item injection.

Parameters: Standard

Returns: Promise<Object> - Menu config:

  • type: 'dashboard-menu'
  • config (Array) - Menu items:
    [
    { name: 'dashboard', visible: true, order: 0 },
    { name: 'contacts', visible: true, order: 1 },
    ...
    ]

Business Logic Flow:

  1. Find Existing Config

  2. Create Default If Missing

    • Use setDefaultDashboardMenu(account.main, user.dashclicks?.general)
    • Main admin: All menu items
    • Main non-admin: Exclude 'dashclicks'
    • Sub-account: Exclude instasites, instareports, projects, dashclicks
  3. Verify Menu Integrity

    • Call verifyDashboardMenu()
    • Check for missing required items
    • Check for extra unauthorized items
    • Update config if discrepancies found
  4. Return Config

Verification Algorithm:

function verifyDashboardMenu(main, admin, storedMenu) {
let expectedMenuItems = setDefaultDashboardMenu(main, admin);
let missingItems = [];
let extraItems = [];

// Find items in stored but not in expected (extra)
for (let storedItem of storedMenu) {
if (!expectedMenuItems.includes(storedItem)) {
extraItems.push(storedItem);
}
}

// Find items in expected but not in stored (missing)
for (let expectedItem of expectedMenuItems) {
if (!storedMenu.includes(expectedItem)) {
missingItems.push(expectedItem);
}
}

if (missingItems.length || extraItems.length) {
return {
passed: false,
new_config: storedMenu.filter(notExtra).concat(missingItems),
};
} else {
return { passed: true };
}
}

updateDashboardMenu({ data, account_id, uid, account, user })โ€‹

Purpose: Updates app drawer menu with validation and auto-correction.

Parameters:

  • data (Array) - Menu item configurations

Returns: Promise<Object> - Updated menu config

Business Logic Flow:

  1. Verify Menu

    • Call verifyDashboardMenu(account.main, user.dashclicks?.general, data)
    • If failed: replace data with corrected config
  2. Update Config

    • Upsert with corrected data

Key Business Rules:

  • Menu items auto-corrected to match account type
  • Cannot remove required items
  • Cannot add unauthorized items

๐ŸŽจ Mandatory & Visible Fieldsโ€‹

Mandatory Fields Injectionโ€‹

Purpose: Adds validation rules for required fields in "add new" forms.

Types of Validators:

  1. Simple Required:

    { type: 'mandatoryConfig', fields: ['name'] }
  2. Conditional Required (one of multiple):

    {
    type: 'mandatoryConfig',
    fields: [
    { field: 'name', empty: ['email'] }, // name required IF email empty
    { field: 'email', empty: ['name'] } // email required IF name empty
    ]
    }

Examples:

  • contacts.people: Require name OR email (at least one)
  • contacts.businesses: Require name
  • deals: Require name and stage_id
  • instasites: Require name
  • inbound: Require updatedAt and User.name

Visible Fields Injectionโ€‹

Purpose: Defines which fields appear in "add new" dialogs by default.

Examples:

contacts.people (default visible):

  • email, name, job_title, phone, lead_status

contacts.businesses (default visible):

  • name, email, phone, website, industry, address.*, social.*

deals (default visible):

  • name, stage_id, status, value, currency, expected_close_date

๐Ÿ› ๏ธ Utility Functionsโ€‹

getDefaults()โ€‹

Purpose: Fetches system-wide default field configurations.

Returns: Promise<Object> - Default configs by type

Business Logic: Query configs collection for type: 'default-user-config-fields'


getKeysAndValues(entity, key = [], kav = [])โ€‹

Purpose: Recursively extracts all leaf keys and values from nested object. Used for dot-notation MongoDB updates.

Algorithm:

function getKeysAndValues(entity, key = [], kav = []) {
if (typeof entity === 'object' && entity !== null) {
if (Array.isArray(entity)) {
kav.push({ key: key.join('.'), value: entity });
} else {
let hasProperties = false;
for (let k in entity) {
hasProperties = true;
key.push(k);
getKeysAndValues(entity[k], key, kav);
key.pop();
}
if (!hasProperties) {
// Handle empty object
kav.push({ key: key.join('.'), value: entity });
}
}
} else {
kav.push({ key: key.join('.'), value: entity });
}
return kav;
}

Use Case: Converting nested object to MongoDB dot-notation updates

Example:

getKeysAndValues({ preferences: { forms: { admin_notification: true } } });
// Returns: [{ key: 'preferences.forms.admin_notification', value: true }]

deepEqual(obj1, obj2)โ€‹

Purpose: Deep equality check for menu item comparison.

Implementation: JSON.stringify(obj1) === JSON.stringify(obj2)


๐Ÿ”€ Integration Pointsโ€‹

Internal Dependenciesโ€‹

  • shared/models/user-config.js - User config storage
  • shared/models/config.js - System default configs
  • shared/models/account.js - Account-level preferences
  • shared/utilities/catch-errors.js - Error helpers
  • ../constants/config.js - Default widget/menu constants

Constants Fileโ€‹

Exports:

  • DASHBOARD_DEFAULTS_WIDGETS - Default dashboard widgets
  • DASHBOARD_DEFAULT_MENU - Default app drawer items
  • allowedTypes - All supported config types

๐Ÿงช Edge Cases & Special Handlingโ€‹

Case: First-Time User Config Accessโ€‹

Condition: User accesses type for first time
Handling: Create config with system defaults, inject mandatory/visible fields
Result: Seamless experience, no null checks needed

Case: New Menu Item Added to Systemโ€‹

Condition: System adds new menu item to DASHBOARD_DEFAULT_MENU
Handling: verifyDashboardMenu() detects missing item, auto-adds on next access
Result: All users get new menu item automatically

Case: Sub-Account User Tries to Add Main-Only Widgetโ€‹

Condition: Widget type validation fails
Handling: Throw forbidden error with specific widget types
Prevention: Frontend should also enforce this

Case: Admin Notification Settings Changeโ€‹

Condition: Non-owner updates admin_notification
Handling: Permission check throws forbidden
Reason: Prevents unauthorized notification silencing

Case: Empty Nested Object in Preferencesโ€‹

Condition: { preferences: { forms: {} } }
Handling: getKeysAndValues() handles empty objects, creates key 'preferences.forms' with value {}
Result: MongoDB update succeeds

Case: Conversation Preferences Missingโ€‹

Condition: Old account lacks preferences.conversations
Handling: getAllConfigs() injects extensive defaults (bubble, availability, required info)
Result: Conversation features work immediately


โš ๏ธ Important Notesโ€‹

  • ๐ŸŽจ UI Driven: Configuration directly controls frontend rendering
  • ๐Ÿ” Permission Layers: Admin settings separated from user settings
  • ๐Ÿ”„ Lazy Creation: Configs created on-demand, never pre-created
  • ๐Ÿงฉ 25+ Types: Supports extensive customization across platform
  • ๐Ÿ“Š Mandatory Validation: Enforces required fields at UI level
  • ๐Ÿ‘๏ธ Visible Control: Defines "add new" dialog fields
  • ๐Ÿ“ฑ Dashboard Widgets: Positioned layout with x, y, w, h
  • ๐Ÿ” App Drawer Menu: Ordered, visibility-controlled navigation
  • ๐Ÿ”„ Auto-Correction: Menu verification ensures integrity
  • ๐Ÿข Account vs User: Some settings at account level (all users), some at user level (personal)
  • ๐Ÿ“ฆ Empty Object Handling: Recursive algorithm handles all nesting levels
  • ๐ŸŽฏ Main vs Sub: Different defaults based on account type
  • ๐Ÿ‘ค Role-Based: DashClicks general users get special menu

  • Parent Module: Users Module
  • Related Service: User Management (link removed - file does not exist)
  • Controller: internal/api/v1/users/controllers/config.js
  • Routes: internal/api/v1/users/routes/config.js
  • Models:
    • User Config (link removed - file does not exist)
    • Config (link removed - file does not exist)
  • Constants: internal/api/v1/users/constants/config.js
๐Ÿ’ฌ

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