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 nameimage(String, optional) - Base64 encoded thumbnail imagebuilder_type(String, required) - Builder platform identifier (e.g., 'duda')builder_account(String, required) - Duda account ID for template ownershiptype(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:
-
Create in Duda
POST ${DUDA_ENDPOINT}/templates/create- Payload:
{ template_name: title } - Headers: Bearer auth + x-duda-token
- Extract
template_namefrom response asbuilder_id
-
Fetch Template Content
GET ${DUDA_ENDPOINT}/templates/${builder_id}/content- Retrieve full template structure for storage
-
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/base64POST /v2/jobswith webp conversion task- Poll job status until complete
- Download converted image
- Upload to S3:
putObjectto${BUCKET}/agency_templates/${builder_id}.webp - Store S3 public URL as thumbnail
-
Save to Database
- Create
AgencyWebsiteTemplatedocument:{
title,
thumbnail: s3Url,
template_content: dudaContent,
builder_type,
builder_id,
builder_account,
type
}
- Create
-
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 pageskip(Number, default: 0) - Number of results to skip (offset)
Returns: Promise<{ data: Array, total: Number }> - Paginated template list with count
Business Logic Flow:
-
Build Query Filter
- Add
title: { $regex: search, $options: 'i' }if search provided - Add
type: typeif type filter specified
- Add
-
Execute Parallel Queries
AgencyWebsiteTemplate.find(filter).skip(skip).limit(limit)- Get pageAgencyWebsiteTemplate.countDocuments(filter)- Get total count- Use
Promise.all()for simultaneous execution
-
Return Paginated Results
data: Array of template documentstotal: 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:
-
Query Database
AgencyWebsiteTemplate.findById(id)
-
Return Result
- Returns full document or null
Error Handling:
- Returns
nullinstead 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 updatetitle(String, optional) - New titleimage(String, optional) - New base64 thumbnailtype(String, optional) - New template typeauthToken(String) - Authentication token
Returns: Promise<Object> - Updated template document
Business Logic Flow:
-
Find Existing Template
AgencyWebsiteTemplate.findById(id)- Throw
notFound()if not exists
-
Create Revision (before changes)
- Save snapshot to
AgencyWebsiteTemplateRevision:{
template_id: id,
old_data: currentTemplate.toObject(),
revision_date: new Date()
}
- Save snapshot to
-
Fetch Latest Content from Duda
GET ${DUDA_ENDPOINT}/templates/${builder_id}/content- Update
template_contentfield
-
Process New Thumbnail (if image provided)
- Same CloudConvert + S3 workflow as
create() - Overwrite existing S3 file with same key
- Update
thumbnailfield with new URL
- Same CloudConvert + S3 workflow as
-
Update Fields
- Apply changes:
title,type,thumbnail,template_content - Save document
- Apply changes:
-
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 deleteauthToken(String) - Authentication token
Returns: Promise<void>
Business Logic Flow:
-
Find Template
AgencyWebsiteTemplate.findById(id)- Throw
notFound()if not exists
-
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"
-
Delete from Duda
DELETE ${DUDA_ENDPOINT}/templates/${builder_id}- Uses dual authentication (Bearer + x-duda-token)
-
Delete from Database
AgencyWebsiteTemplate.findByIdAndDelete(id)
-
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 existbadRequest(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 publishauthToken(String) - Authentication token
Returns: Promise<Object> - API response from Duda
Business Logic Flow:
-
Find Template
AgencyWebsiteTemplate.findById(id)- Throw
notFound()if not exists
-
Call Duda Publish API
POST ${DUDA_ENDPOINT}/templates/${builder_id}/publish- Dual authentication headers
-
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 identifierauthToken(String) - Authentication token
Returns: Promise<Object> - Full template content from Duda
Business Logic Flow:
-
Find Template
AgencyWebsiteTemplate.findById(id)- Throw
notFound()if not exists
-
Fetch from Duda
GET ${DUDA_ENDPOINT}/templates/${builder_id}/content- Dual authentication
-
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:
-
Fetch Template Content
GET ${DUDA_ENDPOINT}/templates/${template_id}/content- Get full template structure
-
Extract Page IDs
- Parse
template.site.pagesarray - Collect all
page_idvalues
- Parse
-
Fetch Each Page (parallel)
- For each page:
GET ${DUDA_ENDPOINT}/templates/${template_id}/pages/${page_id} - Use
Promise.all()for concurrent requests
- For each page:
-
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
-
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:
- Import base64 image
- Create conversion job (target: webp)
- Poll job status
- 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 modelshared/models/agency-website-template-revision.js- Revision history modelshared/models/agency-website.js- Site model (for deletion validation)shared/utilities/catch-errors.js- Error helpers (notFound, badRequest)axios- HTTP clientaws-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
๐ Related Documentationโ
- 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)