Duda Content Service
๐ Overviewโ
The Duda Content service manages site content operations including retrieving content library data, updating content, publishing content changes, and uploading resources like images and files to Duda sites.
Source File: external/Integrations/Duda/services/content.service.js
External API: Duda Content Library API
Primary Use: Site content management and resource uploads
๐๏ธ Collections Usedโ
configโ
- Operations: Read
- Model:
external/models/config.js - Usage Context: Fetch Duda API credentials for authentication
๐ Data Flowโ
sequenceDiagram
participant Client
participant ContentService
participant ConfigDB
participant DudaAPI
Client->>ContentService: Update content request
ContentService->>ConfigDB: Fetch API credentials
ConfigDB-->>ContentService: Return credentials
ContentService->>ContentService: Encode to Base64
ContentService->>ContentService: Validate request body
ContentService->>DudaAPI: POST /sites/:id/content
Note over ContentService,DudaAPI: Authorization: Basic {token}
DudaAPI-->>ContentService: Content updated
ContentService-->>Client: Return success
style ContentService fill:#e3f2fd
style DudaAPI fill:#fff4e6
๐ง Business Logic & Functionsโ
getContent(dudaHeader, id)โ
Purpose: Retrieve the complete content library data for a Duda site
Source: services/content.service.js
External API Endpoint: GET https://api.duda.co/api/sites/multiscreen/:id/content
Parameters:
dudaHeader(ObjectId) - Config ID containing Duda API credentialsid(String) - Duda site ID (site_name)
Returns: Promise<Object>
{
location_data: {
phones: Array,
emails: Array,
label: String,
social_accounts: Object,
address: Object,
geo: Object,
logo_url: String,
business_hours: Object
},
site_texts: Array,
site_images: Array,
business_data: Object,
additional_locations: Array
}
Business Logic Flow:
-
Fetch API Credentials
let query = await Config.findById(dudaHeader);
if (!query) {
throw notFound('Api token not found');
} -
Generate Authentication
const access_token = utils.base64(query); -
Call Duda API
var options = {
method: 'GET',
url: `${DUDA_SITES_DOMAIN}/${id}/content`,
headers: {
'Content-Type': 'application/json',
authorization: `Basic ${access_token}`,
},
};
const response = await axios(options);
return response.data;
API Request Example:
GET https://api.duda.co/api/sites/multiscreen/site_abc123/content
Authorization: Basic {base64_token}
Content-Type: application/json
API Response Example:
{
"location_data": {
"phones": [
{
"phoneNumber": "555-0100",
"label": "Main"
}
],
"emails": ["contact@example.com"],
"label": "Business Name",
"social_accounts": {
"facebook": "https://facebook.com/business",
"twitter": "https://twitter.com/business"
},
"address": {
"streetAddress": "123 Main St",
"city": "City",
"state": "State",
"zipCode": "12345"
}
},
"site_texts": [
{
"label": "Homepage Title",
"text": "Welcome to Our Website"
}
],
"site_images": [
{
"label": "Logo",
"url": "https://example.com/logo.png"
}
]
}
Error Handling:
- 404 Not Found: API credentials not found or site doesn't exist
- 401 Unauthorized: Invalid Duda credentials
Example Usage:
const content = await contentService.getContent(dudaConfigId, 'site_abc123');
console.log('Business Name:', content.location_data.label);
console.log('Phone:', content.location_data.phones[0].phoneNumber);
Key Business Rules:
- Content Library: Returns structured content data that populates site widgets
- Read-Only: Does not modify content, only retrieves
- Complete Data: Returns all content library fields for the site
updateContent(dudaHeader, requestBody, id)โ
Purpose: Update site content library with new data (business info, images, text)
Source: services/content.service.js
External API Endpoint: POST https://api.duda.co/api/sites/multiscreen/:id/content
Parameters:
dudaHeader(ObjectId) - Config IDrequestBody(Object) - Content updateslocation_data(Object, optional) - Business location infosite_texts(Array, optional) - Text content updatessite_images(Array, optional) - Image updatesbusiness_data(Object, optional) - Business data
id(String) - Duda site ID
Returns: Promise<Boolean> - Returns true on success
Business Logic Flow:
-
Fetch Credentials
let query = await Config.findById(dudaHeader);
if (!query) {
throw notFound('Api token not found');
} -
Validate Request Body
if (Object.keys(requestBody).length <= 0) {
throw badRequest('Request body is required.');
} -
Update Content
const access_token = utils.base64(query);
var options = {
method: 'POST',
url: `${DUDA_SITES_DOMAIN}/${id}/content`,
headers: {
'Content-Type': 'application/json',
authorization: `Basic ${access_token}`,
},
data: requestBody,
};
await axios(options);
return true;
API Request Example:
POST https://api.duda.co/api/sites/multiscreen/site_abc123/content
Authorization: Basic {base64_token}
Content-Type: application/json
{
"location_data": {
"phones": [
{
"phoneNumber": "555-0200",
"label": "Main"
}
],
"emails": ["newcontact@example.com"],
"label": "Updated Business Name"
},
"site_texts": [
{
"label": "Homepage Title",
"text": "New Homepage Title"
}
]
}
Content Update Structure:
// Location data updates
{
location_data: {
phones: [{ phoneNumber: String, label: String }],
emails: [String],
label: String, // Business name
social_accounts: {
facebook: String,
twitter: String,
instagram: String,
linkedin: String
},
address: {
streetAddress: String,
city: String,
state: String,
zipCode: String,
country: String
},
geo: {
longitude: Number,
latitude: Number
},
logo_url: String,
business_hours: Object
},
site_texts: [
{
label: String, // Unique identifier
text: String // Content to display
}
],
site_images: [
{
label: String, // Unique identifier
url: String // Image URL
}
]
}
Error Handling:
- 400 Bad Request: Empty request body or invalid data format
- 404 Not Found: API credentials or site not found
- 422 Unprocessable Entity: Invalid content structure (from Duda)
Example Usage:
// Update business phone and email
await contentService.updateContent(
dudaConfigId,
{
location_data: {
phones: [{ phoneNumber: '555-0300', label: 'Main' }],
emails: ['support@newdomain.com'],
},
},
'site_abc123',
);
Key Business Rules:
- Partial Updates: Only provided fields are updated, others remain unchanged
- Label Matching: site_texts and site_images matched by label field
- Not Auto-Published: Content changes are not automatically published to live site
- Requires Publish: Must call publishContent() or publishSites() to make changes live
publishContent(dudaHeader, id)โ
Purpose: Publish content library changes to make them live on the site
Source: services/content.service.js
External API Endpoint: POST https://api.duda.co/api/sites/multiscreen/:id/content/publish
Parameters:
dudaHeader(ObjectId) - Config IDid(String) - Duda site ID
Returns: Promise<Boolean> - Returns true on success
Business Logic Flow:
-
Fetch Credentials
let query = await Config.findById(dudaHeader);
if (!query) {
throw notFound('Api token not found');
} -
Publish Content
const access_token = utils.base64(query);
var options = {
method: 'POST',
url: `${DUDA_SITES_DOMAIN}/${id}/content/publish`,
headers: {
'Content-Type': 'application/json',
authorization: `Basic ${access_token}`,
},
};
await axios(options);
return true;
API Request Example:
POST https://api.duda.co/api/sites/multiscreen/site_abc123/content/publish
Authorization: Basic {base64_token}
Content-Type: application/json
Error Handling:
- 404 Not Found: API credentials or site not found
- 400 Bad Request: Site may not have unpublished content changes
Example Usage:
// Update and publish content
await contentService.updateContent(dudaConfigId, contentUpdates, siteId);
await contentService.publishContent(dudaConfigId, siteId);
// Content is now live
Key Business Rules:
- Content-Only Publish: Publishes only content library changes, not design changes
- Faster than Full Publish: Quicker than full site publish (publishSites())
- Live Immediately: Changes visible on live site immediately after publish
- No Rollback: Cannot undo publish operation
uploadedResources(dudaHeader, requestBody, id)โ
Purpose: Upload images, files, or other resources to a Duda site
Source: services/content.service.js
External API Endpoint: POST https://api.duda.co/api/sites/resources/:id/upload
Parameters:
dudaHeader(ObjectId) - Config IDrequestBody(Object) - Upload dataresource_type(String) - Type: 'IMAGE', 'FILE', etc.src(String) - URL of resource to upload (Duda fetches from URL)alt(String, optional) - Alt text for images
id(String) - Duda site ID
Returns: Promise<Object>
{
url: String, // Uploaded resource URL on Duda CDN
resource_id: String // Unique resource identifier
}
Business Logic Flow:
-
Fetch Credentials
let query = await Config.findById(dudaHeader);
if (!query) {
throw notFound('Api token not found');
} -
Validate Request
if (Object.keys(requestBody).length <= 0) {
throw badRequest('Request body is required.');
} -
Upload Resource
const access_token = utils.base64(query);
var options = {
method: 'POST',
url: `${DUDA_SITES_DOMAIN}/resources/${id}/upload`,
headers: {
'Content-Type': 'application/json',
authorization: `Basic ${access_token}`,
},
data: requestBody,
};
const response = await axios(options);
return response.data;
API Request Example:
POST https://api.duda.co/api/sites/resources/site_abc123/upload
Authorization: Basic {base64_token}
Content-Type: application/json
{
"resource_type": "IMAGE",
"src": "https://example.com/images/photo.jpg",
"alt": "Business Photo"
}
API Response Example:
{
"url": "https://irt-cdn.multiscreensite.com/abc123/photo.jpg",
"resource_id": "resource_xyz789"
}
Resource Types:
- IMAGE: Image files (JPG, PNG, GIF, etc.)
- FILE: Documents, PDFs, etc.
- Other types supported by Duda
Error Handling:
- 400 Bad Request: Empty body or invalid resource_type
- 404 Not Found: Credentials or site not found
- 422 Unprocessable Entity: Invalid src URL or unsupported file type
Example Usage:
// Upload logo image
const uploaded = await contentService.uploadedResources(
dudaConfigId,
{
resource_type: 'IMAGE',
src: 'https://mybucket.s3.amazonaws.com/logo.png',
alt: 'Company Logo',
},
'site_abc123',
);
console.log('Uploaded URL:', uploaded.url);
// Use uploaded URL in content update
await contentService.updateContent(
dudaConfigId,
{
location_data: {
logo_url: uploaded.url,
},
},
'site_abc123',
);
Key Business Rules:
- URL-Based Upload: Duda fetches resource from provided URL (not direct file upload)
- CDN Storage: Uploaded resources stored on Duda's CDN
- Permanent Storage: Resources remain available after upload
- Returns CDN URL: Use returned URL in content updates or design
- Async Processing: Large files may take time to process
๐ Integration Pointsโ
Content Update Workflowโ
// Complete content update and publish flow
async function updateAndPublishSiteContent(siteId, updates) {
// 1. Get current content
const currentContent = await contentService.getContent(dudaConfigId, siteId);
// 2. Merge with updates
const updatedContent = {
...currentContent,
location_data: {
...currentContent.location_data,
...updates.location_data,
},
};
// 3. Update content
await contentService.updateContent(dudaConfigId, updatedContent, siteId);
// 4. Publish changes
await contentService.publishContent(dudaConfigId, siteId);
return true;
}
Image Upload and Updateโ
// Upload image and update site content
async function uploadAndSetLogo(siteId, logoUrl) {
// 1. Upload image to Duda CDN
const uploaded = await contentService.uploadedResources(
dudaConfigId,
{
resource_type: 'IMAGE',
src: logoUrl,
alt: 'Business Logo',
},
siteId,
);
// 2. Update content with new logo URL
await contentService.updateContent(
dudaConfigId,
{
location_data: {
logo_url: uploaded.url,
},
},
siteId,
);
// 3. Publish
await contentService.publishContent(dudaConfigId, siteId);
return uploaded.url;
}
๐งช Edge Cases & Special Handlingโ
Empty Request Bodyโ
Issue: Request body is empty
Handling: Throws 400 Bad Request
if (Object.keys(requestBody).length <= 0) {
throw badRequest('Request body is required.');
}
Missing Credentialsโ
Issue: API credentials not found
Handling: Throws 404 Not Found
if (!query) {
throw notFound('Api token not found');
}
Content Not Publishedโ
Issue: Content updated but not visible on live site
Handling: Must explicitly call publishContent() after updateContent()
Solution:
await contentService.updateContent(dudaConfigId, updates, siteId);
await contentService.publishContent(dudaConfigId, siteId); // Required!
Invalid Resource URLโ
Issue: Upload fails because Duda can't fetch resource from URL
Handling: Duda API returns 422 error
Prevention: Ensure resource URL is publicly accessible before upload
Large File Uploadsโ
Issue: Large files may timeout or take long to process
Handling: No explicit timeout handling in code
Recommendation: Implement retry logic for large uploads
โ ๏ธ Important Notesโ
- Content vs Design: Content API manages content library, not page design/layout
- Publish Required: Content changes not live until published
- Partial Updates: updateContent() only modifies provided fields
- Label Matching: site_texts and site_images use label field as identifier
- URL-Based Upload: uploadedResources() requires publicly accessible URL
- CDN Storage: Uploaded resources hosted on Duda's CDN permanently
- No Direct File Upload: Cannot upload files directly, must provide URL
- Content Publish: publishContent() faster than full site publish
- No Rollback: Cannot undo content changes once published
- Business Hours: Duda has specific format for business_hours object
- Social Accounts: Supports Facebook, Twitter, Instagram, LinkedIn, etc.
- Multiple Phones/Emails: location_data supports arrays of contacts
๐ Related Documentationโ
- Duda Integration Overview: index.md
- Sites Service: sites.md - Full site publish operations
- Duda Content Library API: Official Documentation
- Duda Resources API: Official Documentation
- Config Model:
external/models/config.js