/**
* Helper functions for Action Template operations
*/
/**
* Recursively extract defaultValue entries from UIaction form field definitions.
* Returns a flat object mapping field name → defaultValue.
*
* @param {Object} node - A UIaction node (e.g. { "Form.Input": { paras: { name, defaultValue } } })
* @returns {Object} Map of field name → default value
*/
export const extractUIDefaults = (node) => {
const defaults = {};
if (!node || typeof node !== 'object') return defaults;
for (const [comp, config] of Object.entries(node)) {
if (config?.paras?.name && config.paras.defaultValue != null) {
defaults[config.paras.name] = config.paras.defaultValue;
}
if (Array.isArray(config?.content)) {
config.content.forEach(child => Object.assign(defaults, extractUIDefaults(child)));
}
if (Array.isArray(config?.children)) {
config.children.forEach(child => Object.assign(defaults, extractUIDefaults(child)));
}
}
return defaults;
};
/**
* Recursively builds a translation object by extracting translatable properties
* (content, label, placeholder, html) from a serialized input object and its nested
* children. Ensures that existing entries in the translation object are not overridden.
*
* @param {Object} serialized - The input object containing components and their properties.
* @param {Object} translateObject - The object to store translations, mapping keys to values.
*
* @example
* const serialized = {
* component1: {
* paras: { content: "Hello", label: "Greeting", html: "<p>Welcome</p>" },
* children: [
* { component2: { paras: { content: "World", label: "Planet" } } }
* ]
* }
* };
* const translateObject = {};
* buildTranslateObject(serialized, translateObject);
* console.log(translateObject);
* // { Hello: "Hello", Greeting: "Greeting", "<p>Welcome</p>": "<p>Welcome</p>", World: "World", Planet: "Planet" }
*/
export const buildTranslateObject = (serialized, translateObject) => {
const [[component, componentObject]] = Object.entries(serialized);
if (componentObject.paras) {
const paras = componentObject.paras;
if (paras.content)
translateObject[paras.content] = translateObject[paras.content] ? translateObject[paras.content] : paras.content; // do not override existing entries
if (paras.label)
translateObject[paras.label] = translateObject[paras.label] ? translateObject[paras.label] : paras.label; // do not override existing entries
if (paras.placeholder)
translateObject[paras.placeholder] = translateObject[paras.placeholder] ? translateObject[paras.placeholder] : paras.placeholder; // do not override existing entries
if (paras.html)
translateObject[paras.html] = translateObject[paras.html] ? translateObject[paras.html] : paras.html; // do not override existing entries
}
if (componentObject.content) {
const children = componentObject.content;
// now call it for further content
if (Array.isArray(children)) {
children.forEach((c, Index) => {
buildTranslateObject(c, translateObject);
});
}
else if (typeof children === 'object')
buildTranslateObject(children, translateObject);
}
};
/**
* Validates input for register endpoint
* @param {Object} body - Request body
* @returns {Object} Validation result with success flag and message
*/
export const validateRegisterInput = (body) => {
const { botUID, organizationUIDs = [], template = {} } = body;
if (!botUID || !botUID.match(/^UUID\-[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[1-5][0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}/)) {
return {
success: false,
message: 'botUID is required in the UUID format'
};
}
if (!Array.isArray(organizationUIDs) ) {
return {
success: false,
message: 'organizationUIDs must be an array'
};
}
if (organizationUIDs.length === 0) {
return {
success: false,
message: 'organizationUIDs array cannot be empty'
};
}
if (!template || (typeof template === 'object' && Object.keys(template).length === 0)) {
return {
success: false,
message: 'template cannot be empty'
};
}
return { success: true };
};
/**
* Merges existing template data with new data, preserving customizations
* @param {Object} templateData - New template data
* @param {Object} existingData - Existing template data
* @returns {Object} Merged data
*/
export const mergeTemplateData = (templateData, existingData) => {
// Guard against undefined inputs
if (!templateData || !existingData) {
return templateData || existingData || {};
}
// Start with new template data
const Data = { ...templateData };
// Preserve existing translate only (admin customization)
// name and description come from the bot and should be updated on re-registration
if (existingData.translate !== undefined) {
Data.translate = existingData.translate;
}
// Merge defaults: preserve admin-entered defaults by overwriting new ones with old ones
if (templateData.defaults && existingData.defaults) {
// Start with new defaults, then overwrite with admin customizations
Object.entries(existingData.defaults).forEach(([key, text]) => {
Data.defaults[key] = text;
});
} else if (existingData.defaults) {
// No new defaults, keep all existing
Data.defaults = existingData.defaults;
}
return Data;
};