Shared Models Documentation
Overview
Total Models: 207 Mongoose schemas (excluding node_modules/ and plugins/)
Location: shared/models/
Distribution: Copied to 7 services via npm run copySharedFiles
Pattern: All models use Mongoose with global options from _globalSchemaOptions.js
Naming Convention: [domain]-[entity].js (e.g., account-user.js, billing-charge.js)
Collections: 200+ MongoDB collections
⚠️ CRITICAL: Git Behavior Warning
DO NOT EDIT model files in service directories:
- ❌
internal/api/v1/models/*.js - ❌
external/Integrations/models/*.js - ❌
queue-manager/models/*.js - ❌
conversation-socket/models/*.js - ❌
general-socket/models/*.js - ❌
dashboard-gateway/models/*.js - ❌
tests/models/*.js
These folders are Git-ignored and regenerated on every build. Changes made here will be lost.
✅ ALWAYS EDIT: shared/models/ files only, then run npm run copySharedFiles
Global Schema Configuration
File: shared/models/_globalSchemaOptions.js
Purpose: Consistent Mongoose schema configuration across all 207 models
module.exports = {
timestamps: {
createdAt: 'created_at', // Auto-generate created_at field
updatedAt: 'updated_at', // Auto-generate updated_at field
},
toJSON: {
virtuals: true, // Include virtual properties
versionKey: false, // Exclude __v field
transform: function (doc, ret, game) {
delete ret._id; // Remove _id from JSON output
delete ret.__v; // Remove __v from JSON output
},
},
toObject: {
virtuals: true, // Include virtual properties
versionKey: false, // Exclude __v field
transform: function (doc, ret, game) {
delete ret._id; // Remove _id from object output
delete ret.__v; // Remove __v from object output
},
},
};
Applied By: All models in shared/models/
Benefits:
- Consistent Timestamps: All models use
created_atandupdated_at(notcreatedAt/updatedAt) - Clean JSON: Virtual
idproperty replaces_id, no__vfield - Virtual Support: Mongoose virtuals included in JSON/Object serialization
- Immutable Pattern:
created_atset once,updated_atauto-updates
Model Architecture Principles
Flexible Schema Pattern
Pattern: Most models use { strict: false } to allow dynamic fields beyond defined schema
// Example from account.js
const accountSchema = new mongoose.Schema(
{
business: { type: mongoose.SchemaTypes.ObjectId, ref: 'Contact' },
parent_account: { type: mongoose.SchemaTypes.ObjectId, ref: 'Account' },
manager: { type: mongoose.SchemaTypes.ObjectId, ref: 'User' },
// ... defined fields
},
{
strict: false, // Allows storing arbitrary fields
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' },
},
);
Benefits:
- Rapid Development: Add fields without schema migrations
- Integration Flexibility: Store varying external API responses
- Backward Compatibility: Old code won't break if new fields added
Trade-offs:
- Less validation at database level
- Requires application-level validation
- Can accumulate unused fields over time
Custom JSON Transformation
Pattern: All models use toJSON.js plugin for consistent output
// Applied to every model
schema.methods.toJSON = require('./toJSON');
// Transforms:
// - Removes _id and __v fields
// - Includes virtual properties
// - Adds 'id' virtual field
Rich Model Methods
Pattern: Complex models include custom instance methods
// Example from user.js
userSchema.methods.updateConversationsStatus = async function (sockets, status) {
// Complex logic for updating user presence across socket connections
// Manages conversation_socket_status object
// Updates conversation_active_status (ACTIVE/IDLE/OFFLINE)
// Tracks conversation_last_active timestamp
};
userSchema.methods.getCalendars = function (auth) {
// Retrieves connected Google/Outlook/Internal calendars
// Returns available vs connected calendar distinction
};
Sparse Unique Indexes
Pattern: Used for optional unique fields
// From user.js additional_info
phones: [
{
number: { type: String, unique: true, sparse: true }, // Only enforces uniqueness when present
main: { type: Boolean, default: false },
name: { type: String, default: 'default' },
},
];
Model Categories
Account Management (6 models)
Files: account*.js, workspace-*.js
Purpose: Core account, user, and workspace management
Key Models:
account.js- Main account entity (agencies/clients)account-user.js- Account-user relationshipsuser.js- User authentication and profileworkspace-account.js- Workspace relationshipsworkspace-user.js- User workspace permissionsaccount-credit.js- Account credit balances
Collections: accounts, users, workspaces, accountcredits
API & OAuth (5 models)
Files: api-*.js
Purpose: OAuth 2.0 authentication and API access control
Key Models:
api-session.js- Active user sessions (used by Dashboard Gateway)api-refresh-token.js- OAuth refresh tokensapi-grant.js- Authorization grantsapi-app.js- OAuth client applicationsapi-scope.js- API permission scopes
Collections: apisessions, apirefreshtokens, apigrants, apiapps, apiscopes
Billing & Payments (7 models)
Files: billing-*.js, stripe-subscription.js, consumer-customer-stripe-customer.js
Purpose: Payment processing, subscriptions, and financial transactions
Key Models:
billing-customer.js- Customer billing profilesbilling-subscription.js- Active subscriptionsbilling-charge.js- Payment chargesbilling-refund.js- Refund transactionsbilling-dispute.js- Payment disputesstripe-subscription.js- Stripe-specific subscriptionsstripe-key.js- Stripe API credentials
Collections: billingcustomers, billingsubscriptions, billingcharges, etc.
CRM (9 models)
Files: contact.js, deal.js, pipeline*.js, crm-*.js, activity*.js
Purpose: Customer relationship management - contacts, deals, pipelines, activities
Key Models:
contact.js- CRM contactsdeal.js- Sales deals/opportunitiespipeline.js- Sales pipelinespipeline-stage.js- Pipeline stages/stepsactivity.js- CRM activities (calls, meetings, etc.)crm-log.js- Audit logs for CRM changescrm-note.js- Notes attached to CRM entitiescrm-tag.js- Tags for categorizationautomation.js- CRM workflow automations
Collections: contacts, deals, pipelines, pipelinestages, activities, etc.
Relationships: Contacts → Deals → Pipeline Stages → Pipelines
Conversations & Messaging (11 models)
Files: conversation*.js, message*.js, room*.js, support.*.js
Purpose: Real-time messaging, chat rooms, and support conversations
Key Models:
conversation.js- Main conversation entity (legacy)conversation-conversation.js- V2 conversationsconversation-message.js- Chat messagesconversation-room.js- Chat roomsconversation-channel.js- Communication channelssupport.conversation.js- Support ticket conversationssupport.message.js- Support messagessupport.room.js- Support chat roomsmessage.model.js- Generic message modelroom.model.js- Generic room model
Collections: conversations, messages, rooms, supportconversations, etc.
Used By: Conversation Socket service, General Socket service, Internal API
Store & E-Commerce (14 models)
Files: store-*.js, product.js
Purpose: Product catalog, orders, subscriptions, coupons, payouts
Key Models:
store-product.js- Product catalogstore-price.js- Product pricing tiersstore-order.js- Customer ordersstore-subscription.js- Recurring subscriptionsstore-cart.js- Shopping cartstore-coupon.js- Discount couponsstore-promo-code.js- Promotional codesstore-invoice.js- Invoicesstore-payout.js- Seller payoutsstore-refund.js- Refundsstore-dispute.js- Payment disputesstore-credit.js- Store creditsstore-bundles.js- Product bundles
Collections: storeproducts, storeorders, storesubscriptions, etc.
Funnels (11 models)
Files: funnel*.js, funnels.*.js
Purpose: Marketing funnel builder with templates, steps, and components
Key Models:
funnels.js- Main funnel entityfunnel.step.js- Funnel steps/pagesfunnel.step.components.js- Step components (elements)funnel.blocks.js- Reusable content blocksfunnel.analytics.js- Funnel analytics datafunnels.domains.js- Custom domains for funnelsfunnels.global.templates.js- Global funnel templatesfunnel.global.templates.categories.js- Template categoriesfunnel.global.templates.steps.js- Template stepsfunnel.tags.js- Funnel tags/categories
Collections: funnels, funnelsteps, funnelcomponents, funnelanalytics, etc.
InstaReports (4 models)
Files: instareports*.js
Purpose: Automated marketing reports for clients
Key Models:
instareports.js- Main report configurationinstareports-queue.js- Report generation queueinstareports-additional-information.js- Extra report datainstareports-industry-average.js- Industry benchmark data
Collections: instareports, instareportsqueues, etc.
InstaSites (5 models)
Files: instasite*.js
Purpose: Instant website builder for clients
Key Models:
instasite.js- Main website entityinstasite-queue.js- Website generation queueinstasite-template.js- Website templatesinstasite-template-category.js- Template categoriesinstasites-additional-information.js- Extra site data
Collections: instasites, instasitequeues, instasitetemplates, etc.
Forms (7 models)
Files: forms*.js
Purpose: Form builder, responses, and analytics
Key Models:
forms.js- Form configurationsforms.templates.js- Form templatesforms.categories.js- Form categoriesforms-userresponse.js- Form submissionsforms-sent-requests.js- Form distribution trackingforms-user-categories.js- User-specific categoriesforms-user-tags.js- User-specific tags
Collections: forms, formsresponses, formscategories, etc.
eDocuments (14 models)
Files: edocs-*.js
Purpose: Document management system with templates, categories, and workflows
Key Models:
edocs-document.js- Main document entityedocs-template.js- Document templatesedocs-category.js- User categoriesedocs-default-category.js- System categoriesedocs-file.js- Uploaded filesedocs-file-association.js- File relationshipsedocs-queue.js- Document processing queueedocs-contact.js- Document recipientsedocs-snippets.js- Reusable text snippetsedocs-tag.js- Document tags
Collections: edocsdocuments, edocstemplates, edocscategories, etc.
Calendar & Scheduling (16 models)
Files: event-type*.js, google-calendar*.js, outlook-calendar*.js, availability-scheduler.js, scheduled-event.js, member-meeting.js, internal-calendar.js
Purpose: Meeting scheduling, calendar integrations, and availability management
Key Models:
event-type.js- Event types (meeting types)event-type-team.js- Team event typesscheduled-event.js- Scheduled meetingsmember-meeting.js- Meeting participantsgoogle-calendar.js- Google Calendar syncgoogle-calendar-event.js- Google eventsgoogle-calendar-token.js- Google auth tokensoutlook-calendar.js- Outlook Calendar syncavailability-scheduler.js- Availability rulesinternal-calendar.js- Internal calendar events
Collections: eventtypes, scheduledevents, membermeetings, etc.
Integrations: Google Calendar, Outlook Calendar
Analytics (9 models)
Files: analytics-*.js, campaign-data.js, semrush*.js, google-analytics-token.js
Purpose: Marketing analytics, SEO tracking, campaign data
Key Models:
analytics-seo.js- SEO metrics and rankingsanalytics-seo-config.js- SEO configurationanalytics-googleanalytics-userconfig.js- Google Analytics configanalytics-facebook.ads.userconfig.js- Facebook Ads configanalytics-callrail-userconfig.js- CallRail configanalytics-calltrackingmetrics-userconfig.js- Call tracking configanalytics-tiktokanalytics-userconfig.js- TikTok Analytics configcampaign-data.js- Marketing campaign datasemrush.js- SEMrush data
Collections: analyticsseo, campaigndata, semrush, etc.
Reviews (9 models)
Files: review*.js, reviews*.js
Purpose: Review management, auto-responses, review requests
Key Models:
reviews.js- Customer reviewsreview.configs.js- Review platform configurationsreview.requests.js- Review request campaignsreview.auto-response-rules.js- Auto-response rulesreview.auto-review-request.js- Automated review requestsreview.response-templates.js- Response templatesreview-request-template.js- Request templatesreview.widget.js- Review display widgets
Collections: reviews, reviewconfigs, reviewrequests, etc.
Projects (6 models)
Files: projects-*.js
Purpose: Project management with tasks, files, and reports
Key Models:
projects-tasks.js- Project tasksprojects-files.js- Project filesprojects-notebooks.js- Project notesprojects-reports.js- Project reportsprojects-pulse.js- Project activity feedprojects-dashboard-preferences.js- Dashboard settings
Collections: projectstasks, projectsfiles, projectsnotebooks, etc.
Integration Keys & Tokens (25 models)
Files: *-key.js, *-token.js
Purpose: API credentials and OAuth tokens for third-party integrations
Key Models:
google-ads-token.js,google-analytics-token.js,google-business-token.jsfacebook-ads-keys.js,tik-tok-token.js,bing-ads-token.jsstripe-key.js,sendgrid-key.js,twilio-number.jsduda-key.js,yext-publishers-logo.jshubspot-key.js,salesforce-key.js,pipedrive-key.js,zoho-key.jsmailchimp-key.js,active-campaign-key.js,constant-contact-key.jskeap-key.js,callrail-key.js,calltrackingmetrics-key.jssemrush-auth.js,square-up-token.js,zoom-key.js
Collections: googleadstokens, facebookadskeys, stripekeys, etc.
Pattern: Store encrypted API keys and OAuth tokens for external services
Lead Finder (13 models)
Files: lead-finder-*.js, leads-data.js
Purpose: Lead generation and scraping system
Key Models:
lead-finder-lead.js- Found leadslead-finder-lead-group.js- Lead groupslead-finder-user-lead.js- User-lead associationslead-finder-scraper.js- Scraper configurationslead-finder-scrape-log.js- Scraping logslead-finder-google-rank.js- Google ranking datalead-finder-industry.js- Industry classificationslead-finder-location.js- Geographic dataleads-data.js- Additional lead data
Collections: leadfinderleads, leadfindergroups, leadfinderlogs, etc.
Support & Chat (7 models)
Files: support.*.js
Purpose: Customer support system with conversations, messages, and sessions
Key Models:
support.conversation.js- Support conversationssupport.message.js- Support messagessupport.room.js- Support roomssupport.inbox.js- Support inboxessupport.sessions.js- Support sessionssupport.tokens.js- Support authenticationsupport.message.lock.model.js- Message locking
Collections: supportconversations, supportmessages, supportrooms, etc.
Used By: Conversation Socket service, Internal API
Queue & Background Jobs (9 models)
Files: *-queue.js, queues.js, queue.js
Purpose: Background job processing and queue management
Key Models:
queue.js- Generic queue jobsqueues.js- Queue definitionscontact-queue.js- Contact import queuecsv-queue.js- CSV processing queuewebhook-queue.js- Webhook delivery queuenotification-queue.js- Notification queueinstareports-queue.js- Report generation queueinstasite-queue.js- Site generation queueedocs-queue.js- Document processing queue
Collections: queues, contactqueues, csvqueues, webhookqueues, etc.
Used By: Queue Manager service
Webhooks (5 models)
Files: webhook*.js, *-webhook*.js
Purpose: Webhook management and delivery
Key Models:
webhook.js- Webhook configurationswebhook-log.js- Webhook delivery logswebhook-queue.js- Webhook delivery queuefacebook-webhooks.js- Facebook webhook dataduda-webhook-data.js- Duda webhook data
Collections: webhooks, webhooklogs, facebookwebhooks, etc.
Notifications (5 models)
Files: notification-*.js, fcm-*.js, meeting-*-reminder.js, expo-push-tokens.js
Purpose: Push notifications, email/SMS reminders
Key Models:
notification-queue.js- Notification delivery queuefcm-notification.js- Firebase Cloud Messaging notificationsfcm-token.js- FCM device tokensmeeting-emails-reminder.js- Meeting email remindersmeeting-sms-reminder.js- Meeting SMS remindersexpo-push-tokens.js- Expo push notification tokens
Collections: notificationqueues, fcmnotifications, fcmtokens, etc.
Affiliates (3 models)
Files: affiliate*.js, affiliates-*.js
Purpose: Affiliate program management and payouts
Key Models:
affiliate-payout.js- Affiliate payoutsaffiliate-payout-account.js- Payout account detailsaffiliates-leaderboard.js- Affiliate rankings
Collections: affiliatepayouts, affiliateleaderboard, etc.
OneBalance (3 models)
Files: onebalance*.js
Purpose: Unified balance/credit system
Key Models:
onebalance.js- Balance accountsonebalance-queue.js- Balance update queueonebalance-usage_logs.js- Usage tracking logs
Collections: onebalance, onebalancequeues, onebalanceusagelogs
Miscellaneous (20+ models)
Additional Models:
activity.js- Generic activity trackingannouncement.js- System announcementsautomation.js- Workflow automationscommunication.js- Communication logsconfig.js- System configurationcurrency.js- Currency exchange ratesdashboard-meta.js- Dashboard metadatadnd.js- Do Not Disturb settingsfields-filter.js- Field filtering rulesfilter.js- Saved filterslog.js,logs.js- System logsoffers.js- Special offersproduct.js- Generic productsquotas.js- Usage quotasreminder.js- Remindersshort-url.js- URL shortenersocket.js- Socket connectionstemplate.js,template-default.js- Generic templatestier-override.js- Plan tier overridesunsubscription-list.js- Email unsubscribesuploads.js- File uploadsuser-config.js- User configurationvisitor.js- Anonymous visitors
Model Relationships & Data Flow
Core Data Relationships
erDiagram
Account ||--o{ User : "has members"
Account ||--o{ Contact : "manages"
Account ||--o{ Deal : "owns"
Account ||--o{ Funnel : "creates"
Account ||--o{ InstaReport : "generates"
Account ||--o{ InstaSite : "builds"
User ||--o{ Deal : "assigned to"
User ||--o{ Activity : "performs"
User ||--o{ ApiSession : "has sessions"
Contact ||--o{ Deal : "associated with"
Contact ||--o{ Activity : "logs"
Contact ||--o{ ConversationProspect : "chats in"
Deal }o--|| PipelineStage : "in stage"
PipelineStage }o--|| Pipeline : "belongs to"
ConversationProspect ||--o{ Message : "contains"
ConversationProspect }o--|| Contact : "with"
Funnel ||--o{ FunnelStep : "has steps"
FunnelStep ||--o{ FunnelComponent : "contains"
ApiSession }o--|| ApiRefreshToken : "uses"
ApiSession }o--|| User : "authenticates"
Account Hierarchy Pattern
Multi-Tenant Architecture: All data is scoped to accounts
// Parent-Child Account Relationship
{
_id: ObjectId("agency_account"),
business: ObjectId("contact_id"), // Business contact info
parent_account: null, // Top-level agency
manager: ObjectId("user_id"), // Account manager
main: true
}
{
_id: ObjectId("client_account"),
business: ObjectId("contact_id"),
parent_account: ObjectId("agency_account"), // Child of agency
manager: ObjectId("user_id"),
main: false
}
// Referral tracking
{
_id: ObjectId("referred_account"),
referred_by: ObjectId("referring_account"), // Affiliate tracking
affiliate: {
code: "REF123",
clicks: 45
}
}
Query Pattern: Always filter by account hierarchy
// Find all accounts in hierarchy
const accountIds = await Account.find({
$or: [{ _id: mainAccountId }, { parent_account: mainAccountId }],
}).distinct('_id');
// Query scoped to account
const contacts = await Contact.find({
parent_account: { $in: accountIds },
});
CRM Data Flow
Contact → Deal → Pipeline Flow:
// 1. Contact created
const contact = await Contact.create({
email: 'lead@example.com',
first_name: 'John',
last_name: 'Doe',
parent_account: accountId,
created_by: userId,
});
// 2. Deal associated with contact
const deal = await Deal.create({
contact_id: contact._id,
account_id: accountId,
assigned_to: userId,
pipeline_stage_id: stageId,
title: 'Website Design Project',
value: 5000,
currency: 'USD',
});
// 3. Activity logged
const activity = await Activity.create({
account_id: accountId,
user_id: userId,
contact_id: contact._id,
deal_id: deal._id,
type: 'call',
note: 'Initial consultation call',
duration: 1800, // 30 minutes in seconds
});
// 4. Move deal through pipeline
await deal.updateOne({
pipeline_stage_id: nextStageId,
$push: {
stage_history: {
stage_id: nextStageId,
entered_at: new Date(),
moved_by: userId,
},
},
});
Conversation Integration Pattern
Email/SMS → Conversation → Contact Link:
// When email is sent via mail.js utility
// 1. Find or create conversation
let convo = await ConversationProspect.findOne({
contact_id: contactId,
account_id: accountId,
is_archive: false,
});
if (!convo) {
convo = await ConversationProspect.create({
contact_id: contactId,
account_id: accountId,
created_by: userId,
users: [userId],
unread_count: { [userId]: 0 },
last_contacted: { [contactId]: timestamp },
is_open: true,
});
}
// 2. Create communication record
const communication = await Communication.create({
conversation_id: convo._id,
account_id: accountId,
contact_id: contactId,
sent_by: userId,
type: 'OUTGOING',
module: 'SENDGRID',
message_type: 'EMAIL',
msgID: sendgridMessageId,
body: emailContent,
});
// 3. Update conversation
await convo.updateOne({
last_activity: communication._id,
$inc: {
[`unread_count.${otherUserId}`]: 1, // Increment for other users
},
});
// 4. Emit real-time event
await emitMessage(communication._id);
await emitConversation(convo._id);
Store & Billing Integration
Product → Price → Order → Subscription Flow:
// 1. Product catalog
const product = await StoreProduct.create({
account_id: accountId,
name: 'Website Maintenance',
description: 'Monthly website maintenance service',
type: 'service',
recurring: true,
});
// 2. Pricing tiers
const price = await StorePrice.create({
product_id: product._id,
account_id: accountId,
amount: 99.0,
currency: 'USD',
interval: 'month',
active: true,
});
// 3. Customer order
const order = await StoreOrder.create({
account_id: sellerAccountId,
customer_account: buyerAccountId,
items: [
{
product_id: product._id,
price_id: price._id,
quantity: 1,
},
],
total: 99.0,
status: 'completed',
});
// 4. Create subscription
const subscription = await StoreSubscription.create({
account_id: sellerAccountId,
customer_account: buyerAccountId,
product_id: product._id,
price_id: price._id,
status: 'active',
current_period_start: new Date(),
current_period_end: addMonths(new Date(), 1),
});
// 5. Process billing
const charge = await BillingCharge.create({
customer_id: billingCustomerId,
amount: 99.0,
currency: 'USD',
subscription_id: subscription._id,
status: 'succeeded',
stripe_charge_id: 'ch_xxx',
});
Queue & Background Processing Pattern
Trigger → Queue → Process → Complete:
// 1. User action triggers queue entry
const queueItem = await InstareportsQueue.create({
account_id: accountId,
report_id: reportId,
type: 'generate',
status: 'pending',
priority: 1,
created_by: userId,
});
// 2. Queue Manager picks up job (via cron)
const pendingJobs = await InstareportsQueue.find({
status: 'pending',
attempts: { $lt: 3 },
})
.sort({ priority: -1, created_at: 1 })
.limit(10);
// 3. Process job
for (const job of pendingJobs) {
await job.updateOne({
status: 'processing',
started_at: new Date(),
$inc: { attempts: 1 },
});
try {
// Generate report
const report = await generateReport(job.report_id);
// Mark complete
await job.updateOne({
status: 'completed',
completed_at: new Date(),
result: { pdf_url: report.url },
});
} catch (error) {
await job.updateOne({
status: 'failed',
error: error.message,
failed_at: new Date(),
});
}
}
Integration Keys & OAuth Tokens
Secure Credential Storage Pattern:
// Account-scoped API keys
const googleAdsToken = await GoogleAdsToken.create({
account_id: accountId,
owner: userId,
refresh_token: encryptedRefreshToken, // Encrypted at rest
access_token: encryptedAccessToken,
token_expiration: expiryDate,
customer_id: googleCustomerId,
scopes: ['adwords'],
});
// Retrieve and refresh if needed
const token = await GoogleAdsToken.findOne({
account_id: accountId,
owner: userId,
});
if (new Date() > token.token_expiration) {
// Refresh token logic
const newToken = await refreshGoogleToken(token.refresh_token);
await token.updateOne({
access_token: newToken.access_token,
token_expiration: calculateExpiry(newToken.expires_in),
});
}
// Use in API call
const campaigns = await googleAdsClient.listCampaigns({
customerId: token.customer_id,
accessToken: decryptToken(token.access_token),
});
Schema Design Best Practices
Flexible Schema with Strict: False
Pattern: Allow dynamic fields while maintaining typed core fields
const schema = new mongoose.Schema(
{
// Typed core fields
user_id: { type: mongoose.SchemaTypes.ObjectId, ref: 'User', required: true },
account_id: { type: mongoose.SchemaTypes.ObjectId, ref: 'Account', required: true },
status: { type: String, enum: ['active', 'inactive'], default: 'active' },
// Defined optional fields
metadata: Object,
// ... other fields
},
{
strict: false, // Allow arbitrary additional fields
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' },
},
);
Benefits:
- Rapid Development: Add fields without schema migrations
- Integration Flexibility: Store varying external API responses
- Backward Compatibility: Old code won't break if new fields added
Trade-offs:
- Less validation at database level
- Requires application-level validation
- Can accumulate unused fields over time
Embedded vs Referenced Documents
Embedded Documents (Store data inline):
// Good for: Small, rarely queried independently, strong ownership
{
_id: ObjectId("funnel"),
name: "Landing Page",
steps: [ // Embedded array
{
name: "Hero Section",
order: 1,
components: [/* nested components */]
}
]
}
Referenced Documents (Store ObjectId pointer):
// Good for: Large, queried independently, shared between documents
{
_id: ObjectId("deal"),
contact_id: ObjectId("contact"), // Reference
assigned_to: ObjectId("user"), // Reference
pipeline_stage_id: ObjectId("stage") // Reference
}
// Query with population
const deal = await Deal.findById(dealId)
.populate('contact_id')
.populate('assigned_to')
.populate('pipeline_stage_id');
Index Strategy
Critical Indexes (All models should have):
// Account scoping (multi-tenant)
schema.index({ account_id: 1 });
// User queries
schema.index({ user_id: 1 });
schema.index({ account_id: 1, user_id: 1 });
// Status filtering
schema.index({ status: 1 });
schema.index({ account_id: 1, status: 1 });
// Date range queries
schema.index({ created_at: -1 });
schema.index({ account_id: 1, created_at: -1 });
// Unique constraints
schema.index({ email: 1 }, { unique: true, sparse: true });
Compound Index Order: Most selective field first
// Good: Status has few unique values, account_id is selective
schema.index({ account_id: 1, status: 1, created_at: -1 });
// Bad: status first makes index less effective
schema.index({ status: 1, account_id: 1, created_at: -1 });
Usage Patterns
Importing Models
// In any service (after copySharedFiles)
const Account = require('./models/account');
const User = require('./models/user');
const Contact = require('./models/contact');
const Deal = require('./models/deal');
// Query examples
const account = await Account.findById(accountId);
const users = await User.find({ account_id: accountId });
const deal = await Deal.findOne({ _id: dealId }).populate('contact_id');
Multi-Tenant Query Pattern
// Always scope by account
const contacts = await Contact.find({
account_id: req.auth.account_id, // From JWT token
status: 'active',
});
// Include account hierarchy (parent + children)
const accountIds = await Account.find({
$or: [{ _id: mainAccountId }, { parent_account: mainAccountId }],
}).distinct('_id');
const contacts = await Contact.find({
account_id: { $in: accountIds },
});
Performance Optimization
Lean Queries (Read-only data):
// Without lean: Full Mongoose document (heavy)
const account = await Account.findById(accountId);
// Returns: Mongoose document with methods, getters, setters
// With lean: Plain JavaScript object (light)
const account = await Account.findById(accountId).lean();
// Returns: Plain object, ~2-5x faster
// Use lean when:
// - No need to call instance methods
// - No need to save changes
// - Read-only data for API responses
Projection (Select specific fields):
// Bad: Fetch all fields (including large ones)
const users = await User.find({ account_id: accountId });
// Good: Select only needed fields
const users = await User.find({ account_id: accountId })
.select('first_name last_name email')
.lean();
// Even better: Exclude heavy fields
const users = await User.find({ account_id: accountId })
.select('-password -reset_token') // Exclude sensitive fields
.lean();
Batch Operations:
// Bad: Multiple individual queries
for (const userId of userIds) {
await User.updateOne({ _id: userId }, { last_seen: new Date() });
}
// Good: Single bulk operation
await User.updateMany({ _id: { $in: userIds } }, { last_seen: new Date() });
// Better: Bulk write for different operations
await User.bulkWrite([
{
updateOne: {
filter: { _id: userId1 },
update: { $set: { status: 'active' } },
},
},
{
updateOne: {
filter: { _id: userId2 },
update: { $set: { status: 'inactive' } },
},
},
]);
Performance Metrics
Expected Query Times (with proper indexes):
| Operation | Expected Time | Optimization |
|---|---|---|
findById() | < 10ms | Indexed by _id automatically |
find({ account_id }) | < 50ms | Compound index with account_id |
find() with .populate() | < 100ms | Use .lean() when possible |
aggregate() pipelines | < 200ms | Index pipeline match stages |
| Bulk operations | < 500ms | Batch 1000 documents max |
Memory Management:
- Model Instances: ~2-5KB per document (without lean)
- Lean Documents: ~0.5-1KB per document
- Best Practice: Use
.lean()for large result sets
// Bad: 10,000 full documents = ~30MB memory
const contacts = await Contact.find({ account_id });
// Good: 10,000 lean documents = ~7MB memory
const contacts = await Contact.find({ account_id }).lean();
Security Considerations
Multi-Tenant Isolation
Account-Based Scoping: Every query must filter by account
// Security layer in middleware
router.use((req, res, next) => {
req.account_id = req.auth.account_id; // From JWT
next();
});
// All queries scoped
const data = await Model.find({
account_id: req.account_id, // Required!
});
Data Encryption
At Rest:
- MongoDB encryption at rest (server-side)
- Application-level encryption for API keys/tokens
- Passwords hashed with
crypto.scrypt(never stored plain)
Encrypted Fields Pattern:
// Store encrypted
const encryptedToken = encryptString(apiToken);
await GoogleAdsToken.create({
account_id: accountId,
access_token: encryptedToken,
refresh_token: encryptedRefreshToken,
});
// Retrieve and decrypt
const token = await GoogleAdsToken.findOne({ account_id });
const decryptedToken = decryptString(token.access_token);
Audit Logging
Change Tracking: CRM models include audit fields
{
created_by: ObjectId("user"),
created_at: ISODate("..."),
updated_at: ISODate("..."),
modified_by: ObjectId("user"),
deleted_at: ISODate("..."), // Soft delete
deleted_by: ObjectId("user")
}
Testing Models
Unit Tests
describe('User Model', () => {
it('should update conversation status', async () => {
const user = await User.create({
email: 'test@example.com',
account: accountId,
});
await user.updateConversationsStatus('socket123', 'ACTIVE');
const updated = await User.findById(user._id);
expect(updated.conversation_active_status).toBe('ACTIVE');
});
});
Integration Tests
describe('Deal Model', () => {
it('should track stage history', async () => {
const deal = await Deal.create({
account_id: accountId,
contact_id: contactId,
pipeline_stage_id: stage1Id,
title: 'Test Deal',
});
await deal.updateOne({
pipeline_stage_id: stage2Id,
$push: {
stage_history: {
stage_id: stage2Id,
entered_at: new Date(),
},
},
});
const updated = await Deal.findById(deal._id);
expect(updated.stage_history).toHaveLength(1);
expect(updated.pipeline_stage_id).toBe(stage2Id);
});
});
Related Documentation
- Shared Utilities - Helper functions and business logic
- Shared Middlewares - Express middleware functions
- Shared Overview - Main documentation with distribution process
- Internal API - Service using all models
- Queue Manager - Service using queue models
Resource Type: Mongoose Schemas
Total: 207 models
Collections: 200+ MongoDB collections
Distribution: 7 services receive model copies
Git Strategy: Only shared/models/ is version controlled
Last Updated: October 2025