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 IDaccountId(ObjectId) - Parent agency account ID
Returns: Array with single dashboard object (from aggregation)
Business Logic Flow:
-
Check Existing Dashboard:
const dashboardData = await ProjectsDashboardPreferences.findOne(
{
sub_account_id: subAccountId,
parent_account: accountId,
},
{ _id: 1 },
); -
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
});
} -
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
-
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_SCOPESconstant - ✅ POC Auto-Assignment: Assigned users from orders become POCs
- ✅ Active Status Logic: Users with
reset_tokenmarked inactive
updateDashboardService({ subAccountId, subAccountIds, accountId, allowClientDashboard, users, scopes, unlink })
Purpose: Updates dashboard preferences with complex multi-account linking logic and user management.
Parameters:
subAccountId(ObjectId) - Primary account being updatedsubAccountIds(ObjectId[], optional) - Accounts to link togetheraccountId(ObjectId) - Parent agency accountallowClientDashboard(Boolean, optional) - Enable/disable client dashboardusers(ObjectId[], optional) - Users with dashboard accessscopes(Array, optional) - Permission scopesunlink(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
bulkWritefor performance - ✅ Account Linking: Merges
sub_account_idsacross 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 scopesupdateDashboardutility - Dashboard creation helpersocketEmit- Real-time notification systemcatchAsync- Error handling wrapper
External Services
- Socket.IO -
client_access_removedevent 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: falseusers: [](empty array)scopes: CLIENT_DASHBOARD_SCOPESmainPoc: assigned_user_idsfrom 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_idsarrays - Deduplicates using Set
- Updates all linked accounts with same
sub_account_idsarray
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_removedsocket 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_idsarray 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_dashboardflag controls client dashboard visibility
Related Documentation
- Projects Module Overview - Parent module
- Accounts - Related account management
- Subscriptions - Orders used for POC determination
Last Updated: 2025-10-08
Service Files:services/dashboard.service.js,controllers/dashboard.controller.js
Primary Functions: 2 service functions, 2 controller endpoints