Skip to main content

Templates Controller

๐Ÿ“– Overviewโ€‹

Controller Path: internal/api/v1/templates/Controllers/template.js

The Templates controller provides template management for various communication and workflow types with support for both system-provided (default) and user-created templates. Core responsibilities include:

  • Template CRUD: Create, read, update, and delete user templates
  • System Templates: Distribute default templates to specific main accounts
  • Multi-Source: Combine system and user templates in single response
  • Type Filtering: Email, SMS, proposals, tasks, etc.
  • Category Filtering: Organize templates by business categories
  • Search: Full-text search across title and body
  • Analytics: Track template creation trends by date segments

๐Ÿ—„๏ธ Collections Usedโ€‹

  • templates (link removed - file does not exist) - User-created templates
  • default-templates (link removed - file does not exist) - System-provided templates
  • main-account-default-templates (link removed - file does not exist) - Template distribution to accounts

๐Ÿ”„ Data Flowโ€‹

Template Listing with Multiple Sourcesโ€‹

flowchart TD
A[Get Templates Request] --> B{Source Type?}

B -->|system| C[Query default-templates]
B -->|user| D[Query templates]
B -->|all| E[Query both collections]

C --> F[Check main account permissions]
F --> G{Is main account?}
G -->|Yes| H[Include system templates]
G -->|No| I[Empty array]

D --> J[Filter by account/user]
E --> K[Merge system + user templates]

H --> L[Apply filters]
I --> L
J --> L
K --> L

L --> M{Has type filter?}
M -->|Yes| N[Filter by type]
M -->|No| O[All types]

N --> P{Has category filter?}
P -->|Yes| Q[Filter by category]
P -->|No| R[All categories]

O --> P

Q --> S{Has search query?}
R --> S
S -->|Yes| T[Search title + body]
S -->|No| U[No search]

T --> V[Apply pagination]
U --> V

V --> W[Calculate category counts]
W --> X[Calculate type counts]
X --> Y[Return paginated results]

style C fill:#e3f2fd
style D fill:#e3f2fd
style E fill:#fff4e6
style W fill:#e8f5e9
style X fill:#e8f5e9

Template Creation Flowโ€‹

sequenceDiagram
participant User
participant Controller
participant TemplateModel
participant ValidationMiddleware

User->>Controller: POST /templates
Note over User,Controller: { type, category, title, body }

Controller->>ValidationMiddleware: Validate request body
ValidationMiddleware-->>Controller: Validation result

Controller->>TemplateModel: Create new template
Note over Controller,TemplateModel: account_id + user_id added

TemplateModel->>TemplateModel: Save to database
TemplateModel-->>Controller: Saved template

Controller->>Controller: Transform response
Controller-->>User: Created template

style Controller fill:#e3f2fd
style TemplateModel fill:#fff4e6

๐Ÿ”ง Business Logic & Functionsโ€‹

getTemplates({ src, type, category, page, limit, search, sortOrder, sortField })โ€‹

Purpose: Get templates from multiple sources (system, user, or both) with filtering and pagination

Parameters:

  • src (String) - Source: 'system', 'user', or 'all' (default: 'user')
  • type (String) - Template type filter (email, sms, proposal, task, etc.)
  • category (String) - Category filter
  • page (Number) - Page number (default: 1)
  • limit (Number) - Results per page (default: 10)
  • search (String) - Search query for title/body
  • sortOrder (String) - 'asc' or 'desc' (default: 'desc')
  • sortField (String) - Field to sort by (default: 'createdAt')

Context:

  • this.request - Express request object
  • this.request.headers['x-account-id'] - Account ID
  • this.request.user._id - User ID
  • this.request.user.main_account - Main account ID (for system templates)

Returns:

{
success: true,
message: 'SUCCESS',
data: {
templates: [
{
id: String,
title: String,
body: String,
type: String, // 'email', 'sms', 'proposal', 'task', etc.
category: String,
account_id: ObjectId, // null for system templates
user_id: ObjectId, // null for system templates
createdAt: Date,
updatedAt: Date
}
],
total: Number, // Total matching templates
category_count: { // Count per category
'Business Development': Number,
'Sales': Number,
...
},
type_count: { // Count per type
'email': Number,
'sms': Number,
...
}
}
}

Business Logic Flow:

  1. Determine Source

    const src = this.request.query.src || 'user';
    let templates = [];

    if (src === 'system') {
    // Get system templates only
    const main_account_id = this.request.user.main_account;
    const default_template_ids = await MainAccountDefaultTemplate.distinct('default_template_id', {
    main_account_id,
    });

    templates = await DefaultTemplate.find({
    _id: { $in: default_template_ids },
    });
    } else if (src === 'user') {
    // Get user templates only
    templates = await Template.find({
    account_id: this.request.headers['x-account-id'],
    });
    } else {
    // Get both system and user templates
    const main_account_id = this.request.user.main_account;
    const default_template_ids = await MainAccountDefaultTemplate.distinct('default_template_id', {
    main_account_id,
    });

    const [systemTemplates, userTemplates] = await Promise.all([
    DefaultTemplate.find({ _id: { $in: default_template_ids } }),
    Template.find({ account_id: this.request.headers['x-account-id'] }),
    ]);

    templates = [...systemTemplates, ...userTemplates];
    }
  2. Apply Type Filter

    if (type) {
    templates = templates.filter(template => template.type === type);
    }
  3. Apply Category Filter

    if (category) {
    templates = templates.filter(template => template.category === category);
    }
  4. Apply Search

    if (search) {
    const searchRegex = new RegExp(search, 'i');
    templates = templates.filter(
    template => searchRegex.test(template.title) || searchRegex.test(template.body),
    );
    }
  5. Calculate Counts Before Pagination

    const category_count = {};
    const type_count = {};

    templates.forEach(template => {
    // Category counts
    if (template.category) {
    category_count[template.category] = (category_count[template.category] || 0) + 1;
    }

    // Type counts
    if (template.type) {
    type_count[template.type] = (type_count[template.type] || 0) + 1;
    }
    });
  6. Apply Sorting

    const sortOrder = this.request.query.sortOrder === 'asc' ? 1 : -1;
    const sortField = this.request.query.sortField || 'createdAt';

    templates.sort((a, b) => {
    const aVal = a[sortField];
    const bVal = b[sortField];

    if (aVal < bVal) return -1 * sortOrder;
    if (aVal > bVal) return 1 * sortOrder;
    return 0;
    });
  7. Apply Pagination

    const page = parseInt(this.request.query.page) || 1;
    const limit = parseInt(this.request.query.limit) || 10;
    const startIndex = (page - 1) * limit;
    const endIndex = startIndex + limit;

    const paginatedTemplates = templates.slice(startIndex, endIndex);
  8. Return Response

    return {
    success: true,
    message: 'SUCCESS',
    data: {
    templates: paginatedTemplates.map(t => ({
    id: t._id?.toString(),
    title: t.title,
    body: t.body,
    type: t.type,
    category: t.category,
    account_id: t.account_id,
    user_id: t.user_id,
    createdAt: t.createdAt,
    updatedAt: t.updatedAt,
    })),
    total: templates.length,
    category_count,
    type_count,
    },
    };

Key Business Rules:

  • System Templates: Only available to accounts with main_account_id
  • Source Merging: 'all' combines system + user templates in single list
  • Counts Before Pagination: Category and type counts reflect all matching templates, not just current page
  • Case-Insensitive Search: Searches both title and body fields
  • Default Sorting: Most recent templates first (createdAt desc)

get({ type, search, page, limit, sortOrder, sortField })โ€‹

Purpose: Get user templates only (no system templates) with filtering and pagination

Parameters:

  • type (String) - Template type filter
  • search (String) - Search query
  • page (Number) - Page number (default: 1)
  • limit (Number) - Results per page (default: 10)
  • sortOrder (String) - 'asc' or 'desc' (default: 'desc')
  • sortField (String) - Sort field (default: 'createdAt')

Context:

  • this.request.headers['x-account-id'] - Account ID

Returns:

{
success: true,
message: 'SUCCESS',
data: {
templates: Array,
total: Number
}
}

Business Logic Flow: Similar to getTemplates() but always uses 'user' source


getOne({ templateID })โ€‹

Purpose: Get a single template by ID with ownership validation

Parameters:

  • templateID (String) - Template ID

Context:

  • this.request.headers['x-account-id'] - Account ID

Returns:

{
success: true,
message: 'SUCCESS',
data: {
id: String,
title: String,
body: String,
type: String,
category: String,
account_id: ObjectId,
user_id: ObjectId,
createdAt: Date,
updatedAt: Date
}
}

Business Logic Flow:

  1. Find Template

    const template = await Template.findOne({
    _id: templateID,
    account_id: this.request.headers['x-account-id'],
    });

    if (!template) {
    throw new Error('Template not found');
    }
  2. Return Template

    return {
    success: true,
    message: 'SUCCESS',
    data: {
    id: template._id.toString(),
    title: template.title,
    body: template.body,
    type: template.type,
    category: template.category,
    account_id: template.account_id,
    user_id: template.user_id,
    createdAt: template.createdAt,
    updatedAt: template.updatedAt,
    },
    };

Key Business Rules:

  • Ownership Validation: Template must belong to requesting account
  • Error Handling: Returns error if template not found or belongs to different account

post({ type, body })โ€‹

Purpose: Create a new template for the current account and user

Parameters:

  • type (String) - Template type (email, sms, proposal, task, etc.)
  • body (Object) - Template data:
    • title (String) - Template title
    • body (String) - Template content
    • category (String) - Template category

Context:

  • this.request.headers['x-account-id'] - Account ID
  • this.request.user._id - User ID

Returns:

{
success: true,
message: 'SUCCESS',
data: {
id: String,
title: String,
body: String,
type: String,
category: String,
account_id: ObjectId,
user_id: ObjectId,
createdAt: Date,
updatedAt: Date
}
}

Business Logic Flow:

  1. Create Template

    const newTemplate = await Template.create({
    type: type,
    title: body.title,
    body: body.body,
    category: body.category,
    account_id: this.request.headers['x-account-id'],
    user_id: this.request.user._id,
    });
  2. Return Created Template

    return {
    success: true,
    message: 'SUCCESS',
    data: {
    id: newTemplate._id.toString(),
    title: newTemplate.title,
    body: newTemplate.body,
    type: newTemplate.type,
    category: newTemplate.category,
    account_id: newTemplate.account_id,
    user_id: newTemplate.user_id,
    createdAt: newTemplate.createdAt,
    updatedAt: newTemplate.updatedAt,
    },
    };

Key Business Rules:

  • Auto-Assignment: Account ID and user ID automatically assigned from request context
  • Validation: Middleware validates required fields (title, body, type)

put({ templateID, body })โ€‹

Purpose: Update an existing template with ownership validation

Parameters:

  • templateID (String) - Template ID to update
  • body (Object) - Updated template data:
    • title (String) - Updated title
    • body (String) - Updated content
    • category (String) - Updated category

Context:

  • this.request.headers['x-account-id'] - Account ID

Returns:

{
success: true,
message: 'SUCCESS',
data: {
id: String,
title: String,
body: String,
type: String,
category: String,
account_id: ObjectId,
user_id: ObjectId,
createdAt: Date,
updatedAt: Date
}
}

Business Logic Flow:

  1. Find and Validate Ownership

    const template = await Template.findOne({
    _id: templateID,
    account_id: this.request.headers['x-account-id'],
    });

    if (!template) {
    throw new Error('Template not found');
    }
  2. Update Template

    template.title = body.title || template.title;
    template.body = body.body || template.body;
    template.category = body.category || template.category;

    await template.save();
  3. Return Updated Template

    return {
    success: true,
    message: 'SUCCESS',
    data: {
    id: template._id.toString(),
    title: template.title,
    body: template.body,
    type: template.type,
    category: template.category,
    account_id: template.account_id,
    user_id: template.user_id,
    createdAt: template.createdAt,
    updatedAt: template.updatedAt,
    },
    };

Key Business Rules:

  • Ownership Validation: Only templates belonging to requesting account can be updated
  • Partial Updates: Omitted fields retain existing values
  • System Templates: Cannot be updated (not in templates collection)

delete({ templateID })โ€‹

Purpose: Delete a template with ownership validation

Parameters:

  • templateID (String) - Template ID to delete

Context:

  • this.request.headers['x-account-id'] - Account ID

Returns:

{
success: true,
message: 'SUCCESS'
}

Business Logic Flow:

  1. Find and Validate Ownership

    const template = await Template.findOne({
    _id: templateID,
    account_id: this.request.headers['x-account-id'],
    });

    if (!template) {
    throw new Error('Template not found');
    }
  2. Delete Template

    await Template.deleteOne({ _id: templateID });

    return {
    success: true,
    message: 'SUCCESS',
    };

Key Business Rules:

  • Ownership Validation: Only templates belonging to requesting account can be deleted
  • System Templates: Cannot be deleted (not in templates collection)

getTotalNew({ dateSegment, startDate, endDate })โ€‹

Purpose: Get template count by predefined date segments or custom date range

Parameters:

  • dateSegment (String) - Predefined segment: 'this_week', 'last_week', 'this_month', 'last_six_month', 'this_year'
  • startDate (Date) - Custom start date (if no dateSegment)
  • endDate (Date) - Custom end date (if no dateSegment)

Context:

  • this.request.headers['x-account-id'] - Account ID

Returns:

{
success: true,
message: 'SUCCESS',
data: {
total: Number
}
}

Business Logic Flow:

  1. Calculate Date Range

    const moment = require('moment');
    let startDate, endDate;

    switch (dateSegment) {
    case 'this_week':
    startDate = moment().startOf('week').toDate();
    endDate = moment().endOf('week').toDate();
    break;

    case 'last_week':
    startDate = moment().subtract(1, 'week').startOf('week').toDate();
    endDate = moment().subtract(1, 'week').endOf('week').toDate();
    break;

    case 'this_month':
    startDate = moment().startOf('month').toDate();
    endDate = moment().endOf('month').toDate();
    break;

    case 'last_six_month':
    startDate = moment().subtract(6, 'month').startOf('month').toDate();
    endDate = moment().endOf('month').toDate();
    break;

    case 'this_year':
    startDate = moment().startOf('year').toDate();
    endDate = moment().endOf('year').toDate();
    break;

    default:
    // Use custom dates if provided
    startDate = this.request.query.startDate;
    endDate = this.request.query.endDate;
    }
  2. Count Templates in Date Range

    const count = await Template.countDocuments({
    account_id: this.request.headers['x-account-id'],
    createdAt: {
    $gte: startDate,
    $lte: endDate,
    },
    });
  3. Return Count

    return {
    success: true,
    message: 'SUCCESS',
    data: {
    total: count,
    },
    };

Key Business Rules:

  • Predefined Segments: Standard business time periods for analytics
  • Custom Range: Supports arbitrary date ranges via startDate/endDate
  • Account Scoped: Only counts templates for requesting account

๐Ÿ”€ Integration Pointsโ€‹

Email/SMS Campaignsโ€‹

Load Template:

// Get email templates
const response = await templateController.getTemplates({
src: 'all',
type: 'email',
category: 'Sales',
});

// Apply template to campaign
const campaignContent = response.data.templates[0].body;

Proposal Generationโ€‹

Use Proposal Template:

// Get proposal templates
const response = await templateController.getTemplates({
src: 'system',
type: 'proposal',
});

// Apply template with dynamic data
const proposal = response.data.templates[0].body
.replace('{{client_name}}', clientName)
.replace('{{amount}}', amount);

Task Automationโ€‹

Create Task from Template:

// Get task templates
const response = await templateController.getTemplates({
src: 'user',
type: 'task',
});

// Create task with template content
const task = {
title: response.data.templates[0].title,
description: response.data.templates[0].body,
};

๐Ÿงช Edge Cases & Special Handlingโ€‹

System Templates Only for Main Accountsโ€‹

Non-Main Accounts Get Empty System Templates:

if (src === 'system') {
const main_account_id = this.request.user.main_account;

if (!main_account_id) {
// Non-main accounts have no system templates
return {
success: true,
message: 'SUCCESS',
data: {
templates: [],
total: 0,
category_count: {},
type_count: {},
},
};
}
}

Empty Search Resultsโ€‹

No Matching Templates:

if (templates.length === 0) {
return {
success: true,
message: 'SUCCESS',
data: {
templates: [],
total: 0,
category_count: {},
type_count: {},
},
};
}

Invalid Date Segmentโ€‹

Custom Dates Required:

if (!dateSegment && (!startDate || !endDate)) {
throw new Error('Either dateSegment or startDate/endDate required');
}

Template Not Foundโ€‹

Ownership Validation Failure:

const template = await Template.findOne({
_id: templateID,
account_id: accountID,
});

if (!template) {
throw new Error('Template not found or access denied');
}

โš ๏ธ Important Notesโ€‹

  1. System Templates: Only available to main accounts with assigned default templates via main-account-default-templates collection.

  2. Source Merging: Using src='all' combines system and user templates in a single list, useful for template pickers.

  3. Counts: Category and type counts reflect all matching templates, not just the current page.

  4. In-Memory Operations: Filtering, search, and sorting happen in-memory after fetching templates. For large template sets, consider database-level operations.

  5. Ownership: All CRUD operations validate account ownership. Users cannot access templates from other accounts.

  6. System Template Immutability: System templates cannot be edited or deleted through user endpoints.

  7. Search: Case-insensitive search across both title and body fields using regex.

  8. Date Segments: Predefined segments use moment.js for consistent date calculations (week starts Monday).

  9. Pagination: Applied after filtering/search, so counts are accurate for filtered results.

  10. Type Safety: Template types are not enforced by enum, allowing flexible template types.

  11. Category Management: Categories are free-form strings, allowing custom categorization.

  12. Main Account Distribution: System templates distributed to specific main accounts via junction table.

  • Email Campaigns (link removed - file does not exist) - Email template usage
  • SMS Campaigns (link removed - file does not exist) - SMS template usage
  • Proposals Module (link removed - file does not exist) - Proposal template integration
  • Tasks Module (link removed - file does not exist) - Task template usage
  • Main Accounts (link removed - file does not exist) - System template distribution
๐Ÿ’ฌ

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