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 layoutcontacts_instasite- Contact fields for instasite creationcontacts_instareport- Contact fields for instareport creationdeals- Deal field visibility, card layoutforms- Form configurationtemplates- Email template settings
Marketing & Analytics:
inbound- Inbound campaign configurationcallrail- CallRail integration displaycalltrackingmetrics- Call Tracking Metrics displaygoogle_ads_campaign,google_ads_adgroup,google_ads_ad,google_ads_keywords- Google Ads configurationsfacebook_ads_campaigns,facebook_ads_adsets,facebook_ads_ads- Facebook Ads configurationstiktok_campaign,tiktok_adgroup,tiktok_ads- TikTok Ads configurations
Products & Services:
instasites- Instasite field configurationedocs- E-docs field configurationleadfinder- Lead Finder display settings
System:
dashboard- Dashboard widget configurationdashboard-menu- App drawer menu itemssso- Single sign-on configurationconversations- 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 IDaccount_id(ObjectId) - Account ID
Returns: Promise<Object> - Configuration object with:
_id,user_id,account_id,typefields(Object) - Field visibility/order per entity typecard(Object) - Card layout configuration (left, right, centre)preferences(Object) - Type-specific preferences
Business Logic Flow:
-
Find Existing Config
- Query:
{ user_id: uid, account_id, type }
- Query:
-
Create Default If Missing
- Fetch system defaults from
configscollection (type: 'default-user-config-fields') - Create new
UserConfigwith defaults for type
- Fetch system defaults from
-
Inject Mandatory Fields
- Call
getMandatoryFieldsConditions(type, config) - Adds required field validators (e.g., contacts require name OR email)
- Call
-
Inject Visible Fields
- Call
getVisibleFieldsOnAdd(type, config) - Adds fields visible in "add new" dialogs
- Call
-
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_idconfigurations(Array) - All config types with:- Standard configs (contacts, deals, etc.)
admin_notifications- Account-level notification settingspreferences- Account-level preferencesindoc-steps- Onboarding steps (main accounts only)
Business Logic Flow:
-
Fetch All User Configs
- Query:
{ user_id, account_id }
- Query:
-
Identify Missing Types
- Compare found types against
allowedTypesarray - Create missing configs with defaults
- Compare found types against
-
Special Configs:
- dashboard: Use
getDefaultDashboardWidgets(account.main) - dashboard-menu: Use
setDefaultDashboardMenu(account.main, user.dashclicks?.general)
- dashboard: Use
-
Verify Dashboard Menu
- Call
verifyDashboardMenu()to ensure menu has required items - Update if missing/extra items found
- Call
-
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
- If account lacks
-
Enhance Each Config
- Apply mandatory fields
- Apply visible fields
- Skip dashboard and dashboard-menu (already have correct structure)
-
Append Additional Data
- Add
admin_notificationsfrom account - Add
preferencesfrom account - Add
indoc-steps(if main account)
- Add
-
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 typedata(Object) - Configuration changes:fields(Object) - Field visibility/order updatescard(Object) - Card layout updatespreferences(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:
-
Fetch System Defaults
- Get
default-user-config-fieldsfrom configs collection
- Get
-
Validate Configuration
- Call
validateConfig(defaults[type], data, type) - Merges user changes with system defaults
- Handles field visibility, card layout, preferences
- Call
-
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
forbiddenif violated
- forms.admin_notification: Requires
-
Extract Account-Level Settings
- Pull out
admin_notificationpreferences from:preferences.instasites.admin_notificationpreferences.instareports.admin_notificationpreferences.forms.admin_notification
- Remove from user preferences (move to account)
- Pull out
-
Build Update Queries
- User Config Updates (dot notation):
card.*updatesfields.*updatespreferences.*updates
- Account Updates:
admin_notifications.*updatespreferences.sso.*updates (if SSO changes)preferences.conversations.admin.*updatespreferences.contacts.visibility,preferences.deals.visibility
- User Config Updates (dot notation):
-
Update Account Document
- Apply account-level changes
-
Update User Config Document
- Apply user-level changes (upsert if not exists)
-
Return Results
config: Updated user config with mandatory/visible fieldsadditional_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:
-
Find Existing Config
- Query:
{ user_id: uid, account_id, type: 'dashboard' }
- Query:
-
Create Default If Missing
- Use
getDefaultDashboardWidgets(account.main) - Main accounts get all widgets
- Sub-accounts exclude: instareports, instasites, affiliate
- Use
-
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:
-
Validate Widget Types
- Extract types from data
- Get default allowed types for account
- Calculate diff (types not in defaults)
- Throw
forbiddenif unauthorized types
-
Update Config
- Upsert:
{ user_id: uid, account_id, type: 'dashboard' } - Set
config: data
- Upsert:
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:
-
Find Existing Config
-
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
- Use
-
Verify Menu Integrity
- Call
verifyDashboardMenu() - Check for missing required items
- Check for extra unauthorized items
- Update config if discrepancies found
- Call
-
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:
-
Verify Menu
- Call
verifyDashboardMenu(account.main, user.dashclicks?.general, data) - If failed: replace data with corrected config
- Call
-
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:
-
Simple Required:
{ type: 'mandatoryConfig', fields: ['name'] } -
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
nameORemail(at least one) - contacts.businesses: Require
name - deals: Require
nameandstage_id - instasites: Require
name - inbound: Require
updatedAtandUser.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 storageshared/models/config.js- System default configsshared/models/account.js- Account-level preferencesshared/utilities/catch-errors.js- Error helpers../constants/config.js- Default widget/menu constants
Constants Fileโ
Exports:
DASHBOARD_DEFAULTS_WIDGETS- Default dashboard widgetsDASHBOARD_DEFAULT_MENU- Default app drawer itemsallowedTypes- 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
๐ Related Documentationโ
- 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