Skip to main content

Users Trigger - User Account Synchronization

Overview

File: queue-manager/triggers/users.js
Function: syncUserWithAccount()
Purpose: Real-time synchronization of user profile changes to accounts and orders

When user information changes, this trigger ensures that account owner details, contact information, and order metadata are immediately updated to maintain consistency across the platform.

Trigger Configuration

Watched Collection

  • Collection: Users
  • Events: Insert, Replace, Update
  • Watched Fields:
    • name
    • email
    • phone
    • image

Match Conditions

const matchConditions = {
$or: [
{ operationType: { $in: ['insert', 'replace'] } },
{
$and: [
{ operationType: 'update' },
{
$or: [
{ 'updateDescription.updatedFields.name': { $exists: true } },
{ 'updateDescription.updatedFields.email': { $exists: true } },
{ 'updateDescription.updatedFields.phone': { $exists: true } },
{ 'updateDescription.updatedFields.image': { $exists: true } },
],
},
],
},
],
};

Data Flow

sequenceDiagram
participant API as Internal API
participant USER as Users Collection
participant TRIGGER as users.js Trigger
participant ACCOUNT as Accounts Collection
participant CONTACT as Contacts Collection
participant ORDER as Orders Collection
participant TASK as Projects Tasks

API->>USER: Update user profile
USER->>TRIGGER: Change stream event

alt User is owner
TRIGGER->>ACCOUNT: Update owner object
TRIGGER->>CONTACT: Update business email
end

alt User is manager
TRIGGER->>ORDER: Update manager metadata
end

alt User is specialist
TRIGGER->>ORDER: Update specialist metadata
end

alt User is in assigned_user_ids
TRIGGER->>ORDER: Update responsible array
TRIGGER->>TASK: Update order_metadata
end

Processing Logic

Step 1: Owner User Updates

If the user is an account owner (is_owner: true), update the account:

if (fullDocument.is_owner) {
const updatePayload = {
owner: {
id: fullDocument._id,
name: fullDocument.name,
email: fullDocument.email,
phone: fullDocument.phone || '',
avatar: fullDocument.image,
},
};

await Account.updateOne({ _id: fullDocument.account }, { $set: updatePayload });
}

Step 2: Business Contact Email Sync

For owner users, sync email to business contact:

const business = await Contacts.findOne({
account: fullDocument.account,
type: 'business',
});

if (business && business.email !== fullDocument.email) {
await Contacts.updateOne({ _id: business._id }, { $set: { email: fullDocument.email } });
}

Step 3: Order Metadata Updates (Update Operations Only)

When a user is updated (not inserted), update order metadata:

if (op == 'update') {
const userMetadata = {
id: fullDocument._id,
name: fullDocument.name,
email: fullDocument.email,
phone: fullDocument.phone || '',
image: fullDocument.image,
};

// Update manager metadata
await Order.updateMany(
{ manager: fullDocument._id },
{ $set: { 'metadata.dashclicks_team.manager': userMetadata } },
);

// Update specialist metadata
await Order.updateMany(
{ specialist: fullDocument._id },
{ $set: { 'metadata.dashclicks_team.specialist': userMetadata } },
);
}

Step 4: Responsible Array Updates

For users in assigned_user_ids, rebuild responsible arrays:

const ordersWithUser = await Order.find(
{ assigned_user_ids: fullDocument._id },
{ _id: 1, assigned_user_ids: 1, 'metadata.responsible': 1 },
).lean();

for (const order of ordersWithUser) {
let responsible = Array.isArray(order.metadata?.responsible) ? order.metadata.responsible : [];

const userIndex = responsible.findIndex(
user => user.id && user.id.toString() === fullDocument._id.toString(),
);

if (userIndex >= 0) {
// Update existing user
responsible[userIndex] = userMetadata;
} else {
// Rebuild entire array from assigned_user_ids
responsible = await User.find(
{ _id: { $in: order.assigned_user_ids || [] } },
{
id: '$_id',
_id: 0,
name: 1,
email: 1,
phone: 1,
image: 1,
},
).lean();
}

// Update order and tasks
await Order.updateOne({ _id: order._id }, { $set: { 'metadata.responsible': responsible } });

await ProjectsTask.updateMany(
{ order_id: order._id },
{ $set: { 'order_metadata.responsible': responsible } },
);
}

Collections Updated

Owner User Updates

  • Accounts - owner object (id, name, email, phone, avatar)
  • Contacts - Business contact email field

Non-Owner User Updates (on update only)

  • Orders - metadata.dashclicks_team.manager (if user is manager)
  • Orders - metadata.dashclicks_team.specialist (if user is specialist)
  • Orders - metadata.responsible array (if user in assigned_user_ids)
  • Projects Tasks - order_metadata.responsible (cascaded from orders)

Use Cases

Use Case 1: Owner Profile Update

Scenario: Account owner changes their name and profile picture

Flow:

  1. User updates name from "John Smith" to "John R. Smith"
  2. User uploads new profile picture
  3. Trigger fires immediately
  4. Account owner object updated with new name and avatar
  5. Owner information reflects across dashboard immediately

Use Case 2: Team Member Email Change

Scenario: Specialist changes their email address

Flow:

  1. User updates email from "specialist@old.com" to "specialist@new.com"
  2. Trigger fires (update operation)
  3. All orders where user is specialist get updated metadata
  4. Order metadata now shows new email
  5. Communications use new email address

Use Case 3: Manager Reassignment

Scenario: User assigned as manager to multiple orders updates their phone

Flow:

  1. User updates phone number
  2. Trigger fires for all orders where user is manager
  3. metadata.dashclicks_team.manager.phone updated on all orders
  4. Tasks inherit updated order metadata
  5. Manager phone visible in all order contexts

Error Handling

Account Update Errors

try {
await Account.updateOne({ _id: fullDocument.account }, { $set: updatePayload });
} catch (err) {
console.error(err);
}

Order Metadata Update Errors

try {
await Order.updateMany(/* ... */);
// ... other updates ...
} catch (err) {
console.error('Error updating order metadata:', err);
}

Performance Considerations

Optimizations

  1. Conditional Owner Logic: Only process owner updates for is_owner: true users
  2. Insert vs Update: Only update order metadata on update operations (not inserts)
  3. Lean Queries: All queries use .lean() for performance
  4. Selective Field Updates: Only update specific metadata paths
  5. Parallel Updates: Uses Promise.allSettled() for batch updates

Potential Bottlenecks

  1. High-Volume Orders: Users assigned to many orders trigger multiple updates
  2. Responsible Array Rebuilds: If user not found in array, entire array rebuilt from DB
  3. Tasks Updates: Each order triggers additional task updates

Update Volume Analysis

For a user assigned to N orders:

  • Manager/specialist role: N order updates
  • Assigned user role: N order updates + N task batch updates
  • Owner role: 1 account update + 1 contact update

Owner vs Non-Owner Logic

Owner User Flow

if (fullDocument.is_owner) {
// 1. Update account owner object
await Account.updateOne(/* ... */);

// 2. Sync email to business contact
const business = await Contacts.findOne(/* ... */);
if (business.email !== fullDocument.email) {
await Contacts.updateOne(/* ... */);
}
}

Non-Owner User Flow (Update Only)

if (op == 'update') {
// 1. Update manager metadata
await Order.updateMany({ manager: userId } /* ... */);

// 2. Update specialist metadata
await Order.updateMany({ specialist: userId } /* ... */);

// 3. Update responsible arrays
const orders = await Order.find({ assigned_user_ids: userId } /* ... */);
// ... rebuild responsible arrays ...
}

Responsible Array Rebuilding

Efficient Update (User Found in Array)

const userIndex = responsible.findIndex(
user => user.id && user.id.toString() === fullDocument._id.toString(),
);

if (userIndex >= 0) {
responsible[userIndex] = userMetadata;
}

Full Rebuild (User Not Found)

else {
responsible = await User.find(
{ _id: { $in: order.assigned_user_ids || [] } },
{ id: '$_id', _id: 0, name: 1, email: 1, phone: 1, image: 1 }
).lean();
}

Why rebuild? The user should be in the responsible array (they're in assigned_user_ids) but isn't. This indicates data inconsistency, so the entire array is rebuilt from source.

Downstream Triggers

  • order.js: Order metadata updates may trigger additional order processing
  • projects.js: Task order_metadata updates propagate through project triggers

Complementary Triggers

  • contacts.js: Handles contact-side updates (business contact changes)
  • order.js: Handles order assignment changes that affect users

Monitoring

Key Metrics

  • Trigger Frequency: How often user profiles are updated
  • Order Volume: Number of orders affected per user update
  • Rebuild Rate: How often responsible arrays need full rebuilds
  • Error Rate: Failed updates to accounts/orders/tasks

Logging

console.error('Error updating order metadata:', err);
console.error(err); // Account update errors

Note: Currently uses console.error() - should migrate to logger.error() for consistency.

Best Practices

1. Owner-Only Processing

Only execute owner-specific logic when needed:

if (fullDocument.is_owner) {
// Owner updates
}

2. Operation-Specific Logic

Only process order metadata on updates, not inserts:

if (op == 'update') {
// Order metadata updates
}

3. Email Comparison Before Update

Avoid unnecessary updates:

if (business.email !== fullDocument.email) {
await Contacts.updateOne(/* ... */);
}

4. Graceful Handling of Missing Data

phone: fullDocument.phone || '',

Improvement Opportunities

1. Consistent Logging

Replace console.error() with logger.error():

logger.error({
initiator: 'queue-manager/triggers/users',
error: err,
message: 'Error updating order metadata',
});

2. Batch Processing

Use bulkWrite() for multiple order updates:

const bulkOps = ordersWithUser.map(order => ({
updateOne: {
filter: { _id: order._id },
update: { $set: { 'metadata.responsible': responsible } },
},
}));

await Order.bulkWrite(bulkOps);

3. Incremental Array Updates

Instead of rebuilding entire responsible array, update only the changed user when possible.

Summary

The users trigger maintains synchronization between user profiles, account owner information, and order metadata. It handles both owner-specific updates (account owner object, business contact email) and team member updates (manager, specialist, responsible arrays), ensuring user information stays consistent across all contexts.

Key Characteristics:

  • ⚡ Immediate user profile synchronization
  • 👥 Owner vs non-owner logic separation
  • 📊 Cascades to orders and tasks
  • 🔄 Intelligent array rebuilding
  • 🎯 Operation-specific processing (insert vs update)
💬

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