/**
* This module is meant for handling CMS API requests.
* @module api/parts/mgmt/cms
*/
/** @lends module:api/parts/mgmt/cms */
var cmsApi = {},
common = require('./../../utils/common.js'),
config = require('./../../config.js');
var current_processes = {},
log = common.log('core:cms');
const AVAILABLE_API_IDS = ["server-guides", "server-consents", "server-intro-video", "server-quick-start", "server-guide-config"],
UPDATE_INTERVAL = 1, // hours
TOKEN = "17fa74a2b4b1524e57e8790250f89f44f364fe567f13f4dbef02ef583e70dcdb700f87a6122212bb01ca6a14a8d4b85dc314296f71681988993c013ed2f6305b57b251af723830ea2aa180fc689af1052dd74bc3f4b9b35e5674d4214a8c79695face42057424f0494631679922a3bdaeb780b522bb025dfaea8d7d56a857dba",
BASE_URL = "https://cms.count.ly/api/";
/**
* Get entries for a given API ID from Countly CMS
* @param {params} params - params object
* @param {function} callback - callback function
**/
function fetchFromCMS(params, callback) {
const url = BASE_URL + params.qstring._id;
const pageSize = 100;
var results = [];
/**
* Get a single page of data
* @param {number} pageNumber - page number
**/
function fetchPage(pageNumber) {
var pageUrl = `${url}?pagination[page]=${pageNumber}&pagination[pageSize]=${pageSize}`;
if (params.qstring.populate) {
pageUrl += `&populate=*`;
}
fetch(pageUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + TOKEN
}
})
.then(response => response.json())
.then(responseData => {
const {data, meta} = responseData;
if (data && (data.length > 0 || data.id)) {
// Add data to results
results = results.concat(data);
}
if (meta && meta.pagination && meta.pagination.page < meta.pagination.pageCount) {
// Fetch next page
fetchPage(meta.pagination.page + 1);
}
else {
// All pages fetched or no pagination metadata, invoke callback
callback(null, results);
}
})
.catch(error => {
log.e(error);
callback(error, null);
});
}
fetchPage(1);
}
/**
* Transform and store CMS entries in DB
* @param {params} params - params object
* @param {object} err - error object
* @param {array} data - data array
* @param {function} callback - callback function
**/
function transformAndStoreData(params, err, data, callback) {
const lu = Date.now();
if (err || !data || data.length === 0) {
//Add meta entry
common.db.collection('cms_cache').updateOne({_id: `${params.qstring._id}_meta`}, { $set: {_id: `${params.qstring._id}_meta`, lu, error: !!err}}, { upsert: true }, function() {
callback(null);
});
}
else {
var transformedData = [];
if (params.dataTransformed) {
transformedData = data;
transformedData.forEach(item => {
item.lu = lu;
});
}
else {
for (let i = 0; i < data.length; i++) {
transformedData.push(Object.assign({_id: `${params.qstring._id}_${data[i].id}`, lu}, data[i].attributes));
}
}
var bulk = common.db.collection("cms_cache").initializeUnorderedBulkOp();
for (let i = 0; i < transformedData.length; i++) {
bulk.find({
"_id": transformedData[i]._id
}).upsert().updateOne({
"$set": transformedData[i]
});
}
// Add meta entry
bulk.find({
"_id": `${params.qstring._id}_meta`
}).upsert().replaceOne({
"_id": `${params.qstring._id}_meta`,
"lu": lu
});
// Execute bulk operations to update/insert new entries
bulk.execute(function(err1) {
if (err1) {
callback(err1);
}
// Delete old entries
common.db.collection('cms_cache').deleteMany({'_id': {'$regex': `^${params.qstring._id}`}, 'lu': {'$lt': lu}}, function(err2) {
if (err2) {
callback(err2);
}
callback(null);
});
});
}
}
/**
* Get entries for a given API ID from Countly CMS
* @param {params} params - params object
**/
function syncCMSDataToDB(params) {
// Check if there is a process running
if (!current_processes.id || (current_processes.id && current_processes.id >= new Date(Date.now() - 5 * 60 * 1000))) {
// Set current process
current_processes.id = Date.now();
fetchFromCMS(params, function(err, results) {
transformAndStoreData(params, err, results, function(err1) {
delete current_processes.id;
if (err1) {
log.e('An error occured while storing entries in DB: ' + err1);
}
});
});
}
}
cmsApi.saveEntries = function(params) {
var entries = [];
try {
entries = JSON.parse(params.qstring.entries);
}
catch (ex) {
log.e(params.qstring.entries);
common.returnMessage(params, 400, 'Invalid entries parameter');
return;
}
transformAndStoreData(
Object.assign({dataTransformed: true}, params),
null,
entries,
function(err1) {
if (err1) {
log.e('An error occured while storing entries in DB: ' + err1);
common.returnMessage(params, 500, `Error occured when saving entries to DB: ${err1}`);
}
else {
common.returnMessage(params, 200, 'Entries saved');
}
});
};
/**
* Get entries for a given API ID
* Will request from CMS if entries are stale or not found
* @param {params} params - params object
* @returns {boolean} true
**/
cmsApi.getEntriesWithUpdate = function(params) {
if (!params.qstring._id || AVAILABLE_API_IDS.indexOf(params.qstring._id) === -1) {
common.returnMessage(params, 400, 'Missing or incorrect API _id parameter');
return false;
}
var query = { '_id': { '$regex': `^${params.qstring._id}` } };
try {
params.qstring.query = JSON.parse(params.qstring.query);
}
catch (ex) {
params.qstring.query = null;
}
if (params.qstring.query) {
query = {
$and: [
{ '_id': { '$regex': `^${params.qstring._id}` } },
{
$or: [
{ '_id': `${params.qstring._id}_meta` },
]
}
]
};
for (var cond in params.qstring.query) {
var condition = {};
condition[cond] = params.qstring.query[cond];
query.$and[1].$or.push(condition);
}
params.qstring.query = query;
}
common.db.collection('cms_cache').find(query).toArray(function(err, entries) {
if (err) {
common.returnMessage(params, 500, 'An error occured while fetching CMS entries from DB: ' + err);
return false;
}
let results = {data: entries || []};
if (!entries || entries.length === 0) {
// Force update
results.updating = true;
syncCMSDataToDB(params);
}
else {
const metaEntry = entries.find((item) => item._id.endsWith('meta')) || {};
const updateInterval = UPDATE_INTERVAL * 60 * 60 * 1000;
const timeDifference = Date.now() - (metaEntry.lu || entries[0].lu);
// Update if the update interval has passed
if (timeDifference >= updateInterval) {
results.updating = true;
syncCMSDataToDB(params);
}
// Update if the refresh flag is set and the meta entry does not contain an error
else if (params.qstring.refresh) {
if (metaEntry && !metaEntry.error) {
results.updating = true;
syncCMSDataToDB(params);
}
}
}
// Remove meta entry
results.data = results.data.filter((item) => !item._id.endsWith('meta'));
// Special case for server-guide-config
if (params.qstring._id === 'server-guide-config' && results.data && results.data[0]) {
results.data[0].enableGuides = results.data[0].enableGuides || config.enableGuides;
}
common.returnOutput(params, results);
return true;
});
};
/**
* Get entries for a given API ID
* @param {params} params - params object
* @returns {boolean} true
**/
cmsApi.getEntries = function(params) {
if (!params.qstring._id || AVAILABLE_API_IDS.indexOf(params.qstring._id) === -1) {
common.returnMessage(params, 400, 'Missing or incorrect API _id parameter');
return false;
}
var query = { '_id': { '$regex': `^${params.qstring._id}` } };
try {
params.qstring.query = JSON.parse(params.qstring.query);
}
catch (ex) {
params.qstring.query = null;
}
if (params.qstring.query) {
query = {
$and: [
{ '_id': { '$regex': `^${params.qstring._id}` } },
{
$or: [
{ '_id': `${params.qstring._id}_meta` },
]
}
]
};
for (var cond in params.qstring.query) {
var condition = {};
condition[cond] = params.qstring.query[cond];
query.$and[1].$or.push(condition);
}
params.qstring.query = query;
}
common.db.collection('cms_cache').find(query).toArray(function(err, entries) {
if (err) {
common.returnMessage(params, 500, 'An error occured while fetching CMS entries from DB: ' + err);
return false;
}
let results = {data: entries || []};
// Remove meta entry
results.data = results.data.filter((item) => !item._id.endsWith('meta'));
// Special case for server-guide-config
if (params.qstring._id === 'server-guide-config' && results.data && results.data[0]) {
results.data[0].enableGuides = results.data[0].enableGuides || config.enableGuides;
}
common.returnOutput(params, results);
return true;
});
};
/**
* Clear cache for all API IDs
* @param {params} params - params object
**/
cmsApi.clearCache = function(params) {
var query = {};
if (params.qstring._id) {
query = {'_id': {'$regex': `^${params.qstring._id}`}};
}
common.db.collection('cms_cache').deleteMany(query, function(err) {
if (err) {
common.returnMessage(params, 500, 'An error occured while clearing CMS cache: ' + err);
}
else {
common.returnMessage(params, 200, "CMS cache cleared");
}
});
};
module.exports = cmsApi;