Skip to main content

Dashboard Management

Overview

The Dashboard service manages client dashboard preferences and provides comprehensive data aggregation for dashboard displays. It handles multi-account linking, user permissions, scope management, and automatic dashboard creation with default settings.

Source Files:

  • Service: internal/api/v1/projects/services/dashboard.service.js
  • Controller: internal/api/v1/projects/controllers/dashboard.controller.js

Key Capabilities:

  • Auto-create dashboards with default CLIENT_DASHBOARD_SCOPES
  • Aggregate account, user, and POC information
  • Multi-account linking for unified dashboard view
  • User permission management across linked accounts
  • Client dashboard access control
  • Socket emission for access removal events

Collections Used

projects-dashboard-preferences

Operations: Create, Read, Update
Model: shared/models/projects-dashboard-preferences.js
Usage: Primary storage for dashboard configuration

Key Fields:

{
_id: ObjectId,
sub_account_id: ObjectId, // Primary client account
parent_account: ObjectId, // Agency parent account
sub_account_ids: [ObjectId], // Array of linked accounts
users: [ObjectId], // Allowed users
main_poc: [ObjectId], // Point of contact users
allow_client_dashboard: Boolean,
scopes: [String], // Permission scopes
created_at: Date,
updated_at: Date
}

_store.orders

Operations: Read (aggregation)
Usage: Determines POC from assigned users in managed service orders

_accounts

Operations: Read (lookup)
Usage: Main and sub-account information

_users

Operations: Read (lookup)
Usage: User details, owner status, active status

_api.sessions

Operations: Read (lookup)
Usage: User last login tracking

Business Logic & Functions

Service Layer

getDashboard({ subAccountId, accountId })

Purpose: Retrieves dashboard configuration with enriched account/user data, auto-creating if doesn't exist.

Parameters:

  • subAccountId (ObjectId) - Client sub-account ID
  • accountId (ObjectId) - Parent agency account ID

Returns: Array with single dashboard object (from aggregation)

Business Logic Flow:

  1. Check Existing Dashboard:

    const dashboardData = await ProjectsDashboardPreferences.findOne(
    {
    sub_account_id: subAccountId,
    parent_account: accountId,
    },
    { _id: 1 },
    );
  2. Auto-Create if Missing:

    if (!dashboardData) {
    // Fetch assigned users from managed service orders
    const assignedUsers = await Order.aggregate([
    { $match: {
    buyer_account: subAccountId,
    seller_account: accountId,
    'metadata.product_type': {
    $in: MANAGED_SUBSCRIPTIONS (excluding 'site', 'listings')
    }
    }},
    { $unwind: '$assigned_user_ids' },
    { $group: { _id: '$assigned_user_ids' } }
    ]);

    // Fetch account owner
    const userData = await User.findOne({
    account: subAccountId,
    active: true,
    is_owner: true
    });

    // Create with defaults
    await updateDashboard({
    subAccountId,
    subAccountIds: [subAccountId],
    accountId,
    allowClientDashboard: false,
    mainPoc: uniqueAssignedUserIds,
    users: [], // Start with no users
    scopes: CLIENT_DASHBOARD_SCOPES
    });
    }
  3. Aggregate Dashboard Data:

    • Main Account Lookup: Parent agency info with login URL (custom domain or whitelabel)
    • Sub-Accounts Lookup: All linked account details (name, email, phone, address)
    • User Sessions Lookup: Last login for each user
    • Users Lookup: Active users belonging to linked accounts
    • Main POC Lookup: Point of contact user details with meeting links
  4. Data Transformation:

    {
    id: dashboard._id,
    sub_account_id: ObjectId,
    sub_account_name: "Extracted from sub_accounts array",
    main_account: {
    name: String,
    login_url: String // custom domain or whitelabel
    },
    sub_account_ids: [{
    id, name, email, phone, address
    }],
    users: [{
    id, name, email, image, account,
    is_owner: Boolean,
    active: Boolean, // false if reset_token exists
    last_login: Date // from sessions
    }],
    allow_client_dashboard: Boolean,
    main_poc: [{
    id, name, email, phone, image,
    meeting_link: String
    }],
    scopes: [String],
    created_at: Date,
    updated_at: Date
    }

Key Features:

  • Auto-Creation: Creates dashboard automatically if missing
  • Default Scopes: Uses CLIENT_DASHBOARD_SCOPES constant
  • POC Auto-Assignment: Assigned users from orders become POCs
  • Active Status Logic: Users with reset_token marked inactive

Purpose: Updates dashboard preferences with complex multi-account linking logic and user management.

Parameters:

  • subAccountId (ObjectId) - Primary account being updated
  • subAccountIds (ObjectId[], optional) - Accounts to link together
  • accountId (ObjectId) - Parent agency account
  • allowClientDashboard (Boolean, optional) - Enable/disable client dashboard
  • users (ObjectId[], optional) - Users with dashboard access
  • scopes (Array, optional) - Permission scopes
  • unlink (Boolean) - Whether unlinking accounts

Returns: Updated primary dashboard document

Complex Business Logic:

1. POC Determination:

// Extract assigned users from managed orders (excluding site/listings)
const assignedUsers = await Order.aggregate([
{
$match: {
buyer_account: subAccountId,
seller_account: accountId,
'metadata.product_type': { $in: MANAGED_SUBSCRIPTIONS(filtered) },
},
},
{ $unwind: '$assigned_user_ids' },
{ $group: { _id: '$assigned_user_ids' } },
]);
const uniqueAssignedUserIds = assignedUsers.map(user => user.user_id);

2. Account Linking Logic:

// Determine final linked accounts
let finalSubAccountIds = subAccountIds || existingDashboard?.sub_account_ids || [subAccountId];

if (finalSubAccountIds.length && !unlink) {
// Fetch all dashboards for accounts being linked
const allExistingSubAccounts = await ProjectsDashboardPreferences.find({
sub_account_id: { $in: finalSubAccountIds },
parent_account: accountId,
});

// Merge all sub_account_ids from existing dashboards
finalSubAccountIds = [
...new Set(
allExistingSubAccounts
.flatMap(dashboard => dashboard.sub_account_ids || [])
.map(id => id.toString()),
),
];
}

3. Unlink Detection:

// Find accounts that were linked but are no longer
if (existingDashboard?.sub_account_ids?.length && finalSubAccountIds.length) {
const existingIds = existingDashboard.sub_account_ids.map(id => id.toString());
unlinkedAccounts = existingIds.filter(id => !finalSubAccountIds.includes(id));
}

4. User Merging:

// If users not provided and linking accounts, merge users from all linked dashboards
if (!users && finalSubAccountIds.length > 1) {
const allExistingDashboards = await ProjectsDashboardPreferences.find(
{
sub_account_id: { $in: finalSubAccountIds },
parent_account: accountId,
},
{ users: 1 },
);

const allUsers = allExistingDashboards
.flatMap(dashboard => dashboard.users || [])
.filter(user => user)
.map(user => user.toString());

mergedUsers = [...new Set(allUsers)]; // Remove duplicates
}

5. Removed Users Detection:

if (users && existingDashboard?.users?.length) {
const existingUsers = existingDashboard.users.map(id => id.toString());
removedUsers = existingUsers.filter(id => !users.includes(id));
}

6. Bulk Update Operations:

const bulkOps = [];

// Update all linked accounts
for (const accountId_str of finalSubAccountIds) {
const updateObj = {
sub_account_id: accountId_str,
parent_account: accountId,
sub_account_ids: finalSubAccountIds,
allow_client_dashboard: allowClientDashboard ?? existingDashboard?.allow_client_dashboard,
main_poc: accountId_str === subAccountId ? uniqueAssignedUserIds : existingDashboard?.main_poc,
users: mergedUsers ?? existingDashboard?.users,
scopes: scopes ?? existingDashboard?.scopes,
};

bulkOps.push({
updateOne: {
filter: { sub_account_id: accountId_str, parent_account: accountId },
update: { $set: updateObj },
upsert: true,
},
});
}

// Isolate unlinked accounts
unlinkedAccounts.forEach(unlinkedId => {
bulkOps.push({
updateOne: {
filter: { sub_account_id: unlinkedId, parent_account: accountId },
update: { $set: { sub_account_ids: [unlinkedId] } },
},
});
});

7. Socket Emission for Removed Users:

if (removedUsers.length > 0) {
for (const userId of removedUsers) {
await socketEmit('client_access_removed', [userId.toString()], {
user_id: userId.toString(),
});
}
}

8. Execute and Return:

if (bulkOps.length > 0) {
await ProjectsDashboardPreferences.bulkWrite(bulkOps, { ordered: false });
}

return await ProjectsDashboardPreferences.findOne({
sub_account_id: subAccountId,
parent_account: accountId,
});

Key Features:

  • Atomic Updates: Uses bulkWrite for performance
  • Account Linking: Merges sub_account_ids across dashboards
  • User Merging: Combines users when linking accounts
  • Isolation on Unlink: Unlinked accounts get isolated to single-account array
  • Real-time Notifications: Socket events when users lose access
  • Upsert Logic: Creates dashboard if doesn't exist during update

Controller Layer

getDashboard(req, res)

Route: GET /api/projects/dashboard/:sub_account_id

Authorization: Requires authentication

Request Params:

  • sub_account_id - Client account ID

Response:

{
success: true,
message: 'SUCCESS',
data: [{ ...dashboard data... }] // Array from aggregation
}

updateDashboard(req, res)

Route: PUT /api/projects/dashboard/:sub_account_id

Authorization: Requires authentication, cannot update main account

Validation:

if (subAccountId === req.auth.account_id) {
throw new Error('You cannot update dashboard settings of main account');
}

Request Body:

{
allow_client_dashboard: Boolean,
sub_account_ids: [ObjectId], // Accounts to link
users: [ObjectId], // Users with access
scopes: [String], // Permission scopes
unlink: Boolean // Unlinking operation
}

Response:

{
success: true,
message: 'SUCCESS',
data: { ...updated dashboard... }
}

Data Flow Diagram

flowchart TD
A[GET Dashboard Request] --> B{Dashboard Exists?}
B -->|No| C[Fetch Assigned Users from Orders]
B -->|Yes| D[Aggregate Dashboard Data]
C --> E[Fetch Account Owner]
E --> F[Create Dashboard with Defaults]
F --> D
D --> G[Lookup Main Account]
G --> H[Lookup Sub-Accounts]
H --> I[Lookup User Sessions]
I --> J[Lookup Users]
J --> K[Lookup Main POC]
K --> L[Transform & Return Data]

M[UPDATE Dashboard Request] --> N[Fetch Assigned Users]
N --> O[Get Existing Dashboard]
O --> P{Linking Accounts?}
P -->|Yes| Q[Merge sub_account_ids]
P -->|No| R[Use Provided/Existing IDs]
Q --> S[Detect Unlinked Accounts]
R --> S
S --> T{Users Provided?}
T -->|No & Linking| U[Merge Users from All Dashboards]
T -->|Yes| V[Detect Removed Users]
U --> W[Build Bulk Operations]
V --> W
W --> X{Removed Users?}
X -->|Yes| Y[Emit Socket Events]
X -->|No| Z[Execute Bulk Write]
Y --> Z
Z --> AA[Return Updated Dashboard]

Integration Points

Internal Dependencies

  • MANAGED_SUBSCRIPTIONS - Service type list (from constants)
  • CLIENT_DASHBOARD_SCOPES - Default permission scopes
  • updateDashboard utility - Dashboard creation helper
  • socketEmit - Real-time notification system
  • catchAsync - Error handling wrapper

External Services

  • Socket.IO - client_access_removed event emission

Edge Cases & Special Handling

Case: Main Account Update Attempt

Condition: subAccountId === req.auth.account_id

Handling:

throw new Error('You cannot update dashboard settings of main account');

Case: Missing Dashboard on GET

Condition: No dashboard exists for sub-account

Handling: Auto-creates with:

  • allowClientDashboard: false
  • users: [] (empty array)
  • scopes: CLIENT_DASHBOARD_SCOPES
  • mainPoc: assigned_user_ids from orders

Case: User with Reset Token

Condition: User has reset_token field populated

Handling:

active: {
$cond: {
if: '$$user.reset_token',
then: false,
else: '$$user.active'
}
}

Forces active: false even if database shows active: true

Case: Account Linking

Condition: Multiple accounts in subAccountIds

Handling:

  • Fetches all existing dashboards for those accounts
  • Merges their sub_account_ids arrays
  • Deduplicates using Set
  • Updates all linked accounts with same sub_account_ids array

Case: Account Unlinking

Condition: Account was in sub_account_ids but no longer is

Handling:

  • Detects removed accounts by comparing arrays
  • Creates separate bulk operation to isolate unlinked account
  • Sets sub_account_ids: [unlinkedId] for isolation

Case: User Removal

Condition: User was in users array but no longer is

Handling:

  • Emits client_access_removed socket event
  • Logs error if socket emission fails (non-blocking)
  • Continues with update operation

Case: Bulk Write Failure

Condition: Error during bulkWrite operation

Handling: Uses ordered: false to continue processing remaining operations even if one fails

Important Notes

  • 🔄 Auto-Creation: Dashboard automatically created on first GET if missing
  • 🔗 Multi-Account Linking: Accounts share same sub_account_ids array when linked
  • 👥 User Merging: Users automatically merged when accounts are linked
  • 🔌 Real-time Updates: Socket events notify removed users immediately
  • Performance: Bulk operations update multiple documents efficiently
  • 🛡️ Main Account Protection: Main accounts cannot update their own dashboard settings
  • 📊 POC Auto-Assignment: Main POCs derived from order assigned users
  • 🔒 Access Control: allow_client_dashboard flag controls client dashboard visibility

Last Updated: 2025-10-08
Service Files: services/dashboard.service.js, controllers/dashboard.controller.js
Primary Functions: 2 service functions, 2 controller endpoints

💬

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