Skip to main content

Template Management

๐Ÿ“– Overviewโ€‹

internal/api/v1/sites/services/templates.js handles website template lifecycle management, including template creation, listing, updating, deleting, and publishing with deep Duda platform integration. Provides template revision control and image management utilities.

File Path: internal/api/v1/sites/services/templates.js

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

๐Ÿ“š Full Schema: See Database Collections Documentation

agency_website_templatesโ€‹

  • Operations: Full CRUD operations for template management
  • Model: shared/models/agency-website-template.js
  • Usage Context: Stores template metadata, builder IDs, thumbnails, and type classifications

agency_website_template_revisionsโ€‹

  • Operations: Create for revision history tracking
  • Model: shared/models/agency-website-template-revision.js
  • Usage Context: Maintains template version history for rollback and audit

๐Ÿ”„ Data Flowโ€‹

sequenceDiagram
participant Admin
participant Controller
participant Service
participant Database
participant Duda
participant CloudConvert
participant AWS

Admin->>Controller: POST /templates
Controller->>Service: createTemplate(data)
Service->>Duda: POST /templates/create
Duda-->>Service: Template created
Service->>Duda: GET /templates/{id}/content
Duda-->>Service: Template content

alt Has thumbnail
Service->>CloudConvert: Convert to WEBP
CloudConvert-->>Service: Converted image
Service->>AWS: Upload to S3
AWS-->>Service: S3 URL
end

Service->>Database: Save template
Database-->>Service: Saved template
Service-->>Controller: Template data
Controller-->>Admin: Success response

๐Ÿ”ง Business Logic & Functionsโ€‹

Template CRUDโ€‹


create({ title, image, builder_type, builder_account, type, authToken })โ€‹

Purpose: Creates a new website template in both Duda platform and DashClicks database, with automatic thumbnail processing and S3 upload.

Parameters:

  • title (String, required) - Template display name
  • image (String, optional) - Base64 encoded thumbnail image
  • builder_type (String, required) - Builder platform identifier (e.g., 'duda')
  • builder_account (String, required) - Duda account ID for template ownership
  • type (String, required) - Template category:
    • 'informational' - Business information sites
    • 'ecommerce' - Online stores
  • authToken (String) - Authentication token for Duda API

Returns: Promise<Object> - Created template document with processed thumbnail

Business Logic Flow:

  1. Create in Duda

    • POST ${DUDA_ENDPOINT}/templates/create
    • Payload: { template_name: title }
    • Headers: Bearer auth + x-duda-token
    • Extract template_name from response as builder_id
  2. Fetch Template Content

    • GET ${DUDA_ENDPOINT}/templates/${builder_id}/content
    • Retrieve full template structure for storage
  3. Process Thumbnail (if image provided)

    • Parse base64: Extract format (jpeg/png/gif) and data
    • Convert to WEBP: Use CloudConvert API for optimization
      • POST https://api.cloudconvert.com/v2/import/base64
      • POST /v2/jobs with webp conversion task
      • Poll job status until complete
      • Download converted image
    • Upload to S3: putObject to ${BUCKET}/agency_templates/${builder_id}.webp
    • Store S3 public URL as thumbnail
  4. Save to Database

    • Create AgencyWebsiteTemplate document:
      {
      title,
      thumbnail: s3Url,
      template_content: dudaContent,
      builder_type,
      builder_id,
      builder_account,
      type
      }
  5. Return Created Template

Key Business Rules:

  • Template creation is atomic: Duda first, then database
  • Thumbnail conversion always to WEBP for optimization
  • S3 naming convention: agency_templates/{builder_id}.webp
  • Template content stored for quick loading without Duda API calls

Error Handling:

  • Duda API errors bubble up with HTTP status codes
  • CloudConvert failures logged but don't block template creation
  • S3 upload failures result in template saved without thumbnail

Example Usage:

const template = await templateService.create({
title: 'Restaurant Website',
image: 'data:image/jpeg;base64,/9j/4AAQ...',
builder_type: 'duda',
builder_account: 'acc_123',
type: 'informational',
authToken: req.authToken,
});
// Returns: { _id, title, thumbnail: 's3://...', builder_id, ... }

Side Effects:

  • โš ๏ธ Creates template in external Duda platform
  • โš ๏ธ Uploads image to AWS S3 bucket
  • โš ๏ธ Uses CloudConvert API credits

getAll({ search, type, limit, skip })โ€‹

Purpose: Retrieves paginated list of templates with optional filtering by title and type.

Parameters:

  • search (String, optional) - Filter by title (case-insensitive regex)
  • type (String, optional) - Filter by template type ('informational' or 'ecommerce')
  • limit (Number, default: 10) - Number of results per page
  • skip (Number, default: 0) - Number of results to skip (offset)

Returns: Promise<{ data: Array, total: Number }> - Paginated template list with count

Business Logic Flow:

  1. Build Query Filter

    • Add title: { $regex: search, $options: 'i' } if search provided
    • Add type: type if type filter specified
  2. Execute Parallel Queries

    • AgencyWebsiteTemplate.find(filter).skip(skip).limit(limit) - Get page
    • AgencyWebsiteTemplate.countDocuments(filter) - Get total count
    • Use Promise.all() for simultaneous execution
  3. Return Paginated Results

    • data: Array of template documents
    • total: Total matching documents (for pagination UI)

Key Business Rules:

  • Default pagination: 10 items per page
  • Search is case-insensitive and partial match
  • Type filter is exact match

Example Usage:

const { data, total } = await templateService.getAll({
search: 'restaurant',
type: 'informational',
limit: 20,
skip: 0,
});
// Returns: { data: [...], total: 47 }

getById(id)โ€‹

Purpose: Retrieves a single template by ID with full details.

Parameters:

  • id (ObjectId) - Template identifier

Returns: Promise<Object|null> - Template document or null if not found

Business Logic Flow:

  1. Query Database

    • AgencyWebsiteTemplate.findById(id)
  2. Return Result

    • Returns full document or null

Error Handling:

  • Returns null instead of throwing error (allows graceful handling in controller)

update({ id, title, image, type, authToken })โ€‹

Purpose: Updates an existing template with new metadata and optionally new thumbnail. Creates revision history.

Parameters:

  • id (ObjectId, required) - Template to update
  • title (String, optional) - New title
  • image (String, optional) - New base64 thumbnail
  • type (String, optional) - New template type
  • authToken (String) - Authentication token

Returns: Promise<Object> - Updated template document

Business Logic Flow:

  1. Find Existing Template

    • AgencyWebsiteTemplate.findById(id)
    • Throw notFound() if not exists
  2. Create Revision (before changes)

    • Save snapshot to AgencyWebsiteTemplateRevision:
      {
      template_id: id,
      old_data: currentTemplate.toObject(),
      revision_date: new Date()
      }
  3. Fetch Latest Content from Duda

    • GET ${DUDA_ENDPOINT}/templates/${builder_id}/content
    • Update template_content field
  4. Process New Thumbnail (if image provided)

    • Same CloudConvert + S3 workflow as create()
    • Overwrite existing S3 file with same key
    • Update thumbnail field with new URL
  5. Update Fields

    • Apply changes: title, type, thumbnail, template_content
    • Save document
  6. Return Updated Template

Key Business Rules:

  • Revision created BEFORE modifications (audit trail)
  • Template content always refreshed from Duda on update
  • Thumbnail replacement overwrites S3 file (no orphaned files)
  • Only provided fields are updated (partial updates allowed)

Error Handling:

  • notFound(404): Template doesn't exist
  • Duda API errors bubble up
  • Revision creation failures logged but don't block update

Side Effects:

  • โš ๏ธ Creates revision record in database
  • โš ๏ธ Fetches fresh content from Duda API
  • โš ๏ธ May upload new image to S3
  • โš ๏ธ May use CloudConvert API credits

deleteTemplate({ id, authToken })โ€‹

Purpose: Permanently deletes a template from both DashClicks database and Duda platform. Validates no published sites are using it.

Parameters:

  • id (ObjectId) - Template to delete
  • authToken (String) - Authentication token

Returns: Promise<void>

Business Logic Flow:

  1. Find Template

    • AgencyWebsiteTemplate.findById(id)
    • Throw notFound() if not exists
  2. Check for Published Sites (safety check)

    • AgencyWebsite.findOne({ template_id: id, status: 'PUBLISHED' })
    • Throw badRequest() if found: "This template is currently being used by a published site"
  3. Delete from Duda

    • DELETE ${DUDA_ENDPOINT}/templates/${builder_id}
    • Uses dual authentication (Bearer + x-duda-token)
  4. Delete from Database

    • AgencyWebsiteTemplate.findByIdAndDelete(id)
  5. Note: S3 thumbnail NOT deleted (orphaned file remains)

Key Business Rules:

  • Cannot delete templates in use by published sites
  • External deletion (Duda) before database deletion
  • No cascade delete of revisions (they remain for audit)

Error Handling:

  • notFound(404): Template doesn't exist
  • badRequest(400): Template in use by published site
  • Duda API errors bubble up

Known Issues:

  • ๐Ÿšจ S3 Cleanup: Thumbnail image left on S3 after deletion (potential cost accumulation)
  • ๐Ÿšจ Transaction: Should wrap in MongoDB transaction (Duda delete fails after DB delete = inconsistency)

Side Effects:

  • โš ๏ธ Permanently removes template from Duda platform
  • โš ๏ธ Deletes database record
  • โš ๏ธ Orphans S3 thumbnail file

Template Publishing & Contentโ€‹


publishTemplate({ id, authToken })โ€‹

Purpose: Publishes a template in Duda, making it available for site creation by users.

Parameters:

  • id (ObjectId) - Template to publish
  • authToken (String) - Authentication token

Returns: Promise<Object> - API response from Duda

Business Logic Flow:

  1. Find Template

    • AgencyWebsiteTemplate.findById(id)
    • Throw notFound() if not exists
  2. Call Duda Publish API

    • POST ${DUDA_ENDPOINT}/templates/${builder_id}/publish
    • Dual authentication headers
  3. Return Duda Response

    • Pass through raw Duda API response

Key Business Rules:

  • No database status change (Duda is source of truth for publish state)
  • Published templates appear in user's template gallery

Error Handling:

  • notFound(404): Template doesn't exist
  • Duda API errors (e.g., already published) bubble up

getTemplateContent({ id, authToken })โ€‹

Purpose: Fetches the full content structure of a template from Duda platform. Used for cloning or inspection.

Parameters:

  • id (ObjectId) - Template identifier
  • authToken (String) - Authentication token

Returns: Promise<Object> - Full template content from Duda

Business Logic Flow:

  1. Find Template

    • AgencyWebsiteTemplate.findById(id)
    • Throw notFound() if not exists
  2. Fetch from Duda

    • GET ${DUDA_ENDPOINT}/templates/${builder_id}/content
    • Dual authentication
  3. Return Raw Content

    • Pass through Duda's response unchanged

Key Business Rules:

  • Always fetches live data from Duda (not cached content from DB)
  • Used when creating sites from templates

Utility Functionsโ€‹


_getTemplateImages(template_id, authToken)โ€‹

Purpose: Internal utility to retrieve all images used within a template's pages. Crawls template structure recursively.

Parameters:

  • template_id (String) - Duda template ID (builder_id)
  • authToken (String) - Authentication token

Returns: Promise<Array<String>> - Array of image URLs from template

Business Logic Flow:

  1. Fetch Template Content

    • GET ${DUDA_ENDPOINT}/templates/${template_id}/content
    • Get full template structure
  2. Extract Page IDs

    • Parse template.site.pages array
    • Collect all page_id values
  3. Fetch Each Page (parallel)

    • For each page: GET ${DUDA_ENDPOINT}/templates/${template_id}/pages/${page_id}
    • Use Promise.all() for concurrent requests
  4. Crawl Page Content for Images

    • Recursively traverse page objects
    • Look for properties ending with '_image', 'src', or 'url'
    • Extract values starting with 'http'
    • Collect unique URLs using Set
  5. Return Deduplicated URLs

Key Business Rules:

  • Recursive traversal handles nested page structures
  • Multiple image property patterns detected
  • Deduplication prevents duplicate URLs

Algorithm:

function crawlForImages(obj, images) {
for (const [key, value] of Object.entries(obj)) {
if (
typeof value === 'string' &&
(key.endsWith('_image') || key === 'src' || key === 'url') &&
value.startsWith('http')
) {
images.add(value);
}
if (typeof value === 'object' && value !== null) {
crawlForImages(value, images); // Recursive
}
}
}

Use Cases:

  • Template migration
  • Asset inventory
  • Duplicate image detection
  • Pre-warming CDN cache

๐Ÿ”€ Integration Pointsโ€‹

External Servicesโ€‹

Duda Platformโ€‹

  • Endpoint: process.env.DUDA_INTERNAL_ENDPOINT
  • Authentication: Dual token (Bearer + x-duda-token)
  • Operations:
    • Template CRUD: Create, update, delete, publish
    • Content retrieval: Get full template structure
    • Page content: Fetch individual page data

Authentication Headers:

{
authorization: `Bearer ${authToken}`,
'x-account-id': 'system', // Or account_id for user operations
'x-duda-token': process.env.DUDA_TOKEN
}

CloudConvert APIโ€‹

  • Purpose: Image format conversion (any format โ†’ WEBP)
  • API Key: process.env.CLOUDCONVERT_API_KEY
  • Workflow:
    1. Import base64 image
    2. Create conversion job (target: webp)
    3. Poll job status
    4. Download converted file
  • Cost: Per-conversion charge

AWS S3โ€‹

  • Bucket: process.env.AWS_S3_BUCKET
  • Region: process.env.AWS_REGION
  • Path Pattern: agency_templates/{builder_id}.webp
  • ACL: 'public-read' for direct access
  • Content-Type: 'image/webp'

Upload Configuration:

{
Bucket: process.env.AWS_S3_BUCKET,
Key: `agency_templates/${builder_id}.webp`,
Body: imageBuffer,
ContentType: 'image/webp',
ACL: 'public-read'
}

Internal Dependenciesโ€‹

  • shared/models/agency-website-template.js - Template data model
  • shared/models/agency-website-template-revision.js - Revision history model
  • shared/models/agency-website.js - Site model (for deletion validation)
  • shared/utilities/catch-errors.js - Error helpers (notFound, badRequest)
  • axios - HTTP client
  • aws-sdk - S3 operations

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

Case: Template Deletion with Published Sitesโ€‹

Condition: Admin attempts to delete template while published sites use it
Handling: Throw badRequest(400) with message: "This template is currently being used by a published site"
Reason: Prevents breaking active websites

Case: CloudConvert API Failureโ€‹

Condition: Image conversion service unavailable or errors
Handling: Template creation continues WITHOUT thumbnail
Reason: Template functionality more important than thumbnail

Case: Duda API Timeout During Updateโ€‹

Condition: Template content fetch times out
Handling: Error bubbles up, update aborted, revision still created
Recovery: Admin can retry update

Case: S3 Upload Failureโ€‹

Condition: AWS credentials invalid or network error
Handling: Template saved without thumbnail URL
Recovery: Can re-upload thumbnail via update operation

Case: Duplicate Image URLs in Templateโ€‹

Condition: Same image used multiple times in template
Handling: Set deduplication in _getTemplateImages()
Result: Each URL appears once in result array


โš ๏ธ Important Notesโ€‹

  • ๐Ÿšจ S3 Orphan Files: Deleted templates leave thumbnails on S3 (cleanup needed)
  • ๐Ÿšจ No Transaction: Template deletion not atomic (Duda + DB can desync)
  • ๐ŸŽฏ WEBP Only: All thumbnails converted to WEBP for performance
  • ๐Ÿ”„ Revision History: Update operations create audit trail automatically
  • ๐Ÿ” Dual Auth: All Duda operations require both tokens
  • ๐Ÿ“ธ Image Crawling: Recursive algorithm finds all image references in templates
  • ๐Ÿ’ฐ CloudConvert Cost: Each thumbnail upload consumes API credits
  • ๐Ÿ—„๏ธ Content Storage: Template content cached in DB to reduce Duda API calls
  • โšก Parallel Processing: Page fetching and counting use concurrent requests

  • Parent Module: Sites Module
  • Related Service: Site Management (link removed - file does not exist)
  • Controller: internal/api/v1/sites/controllers/templates.js
  • Routes: internal/api/v1/sites/routes/templates.js
  • Models:
    • Agency Website Template (link removed - file does not exist)
    • Template Revisions (link removed - file does not exist)
๐Ÿ’ฌ

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