diff --git a/README.md b/README.md index 8c12cfbd..d4f0a9d9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/CloudBoost/cloudboost.svg?branch=master)](https://travis-ci.org/CloudBoost/cloudboost) [![OpenCollective](https://opencollective.com/cloudboost/backers/badge.svg)](#backers) -CloudBoost is the Complete NoSQL Database Service for your app. **Think of CloudBoost as Parse + Firebase + Algolia + Iron.io all combined into one** : +CloudBoost is the complete serverless platform for your app. **Think of CloudBoost as Parse + Firebase + Algolia + Iron.io all combined into one** : - Data-Storage / JSON Storage / BLOB Storage - 100% data ownership - Realtime diff --git a/api/tables/CloudObjects.js b/api/tables/CloudObjects.js index c1fe6de6..86534003 100644 --- a/api/tables/CloudObjects.js +++ b/api/tables/CloudObjects.js @@ -1,22 +1,19 @@ - /* # CloudBoost - Core Engine that powers Bakend as a Service -# (c) 2014 HackerBay, Inc. +# (c) 2014 HackerBay, Inc. # CloudBoost may be freely distributed under the Apache 2 License */ - var customHelper = require('../../helpers/custom.js'); module.exports = function() { - - global.app.put('/data/:appId/:tableName', function (req, res) { //save a new document into of app - if(req.body && req.body.method=="DELETE"){ + global.app.put('/data/:appId/:tableName', function(req, res) { //save a new document into of app + if (req.body && req.body.method == "DELETE") { /******************DELETE API*********************/ _deleteApi(req, res); /******************DELETE API*********************/ - }else{ + } else { /******************SAVE API*********************/ console.log("SAVE API"); var appId = req.params.appId; @@ -24,25 +21,24 @@ module.exports = function() { var collectionName = req.params.tableName; var appKey = req.body.key || req.params.key; var sdk = req.body.sdk || "REST"; - - global.appService.isMasterKey(appId, appKey).then(function (isMasterKey) { + + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { return global.customService.save(appId, collectionName, document, customHelper.getAccessList(req), isMasterKey); - }).then(function (result) { + }).then(function(result) { console.log('+++ Save Success +++'); console.log(result); res.status(200).send(result); - }, function (error) { + }, function(error) { console.log('++++++ Save Error +++++++'); console.log(error); res.status(400).send(error); }); - global.apiTracker.log(appId,"Object / Save", req.url,sdk); + global.apiTracker.log(appId, "Object / Save", req.url, sdk); /******************SAVE API*********************/ } }); - - + global.app.get('/data/:appId/:tableName/find', _getData); global.app.post('/data/:appId/:tableName/find', _getData); @@ -54,8 +50,8 @@ module.exports = function() { global.app.get('/data/:appId/:tableName/findOne', _findOne); global.app.post('/data/:appId/:tableName/findOne', _findOne); - - global.app.delete('/data/:appId/:tableName', _deleteApi); + + global.app.delete('/data/:appId/:tableName', _deleteApi); function _deleteApi(req, res) { //delete a document matching the console.log("DELETE API"); @@ -65,16 +61,16 @@ module.exports = function() { var appKey = req.body.key || req.param('key'); var sdk = req.body.sdk || "REST"; - global.appService.isMasterKey(appId,appKey).then(function(isMasterKey){ - return global.customService.delete(appId, collectionName, document, customHelper.getAccessList(req),isMasterKey); + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { + return global.customService.delete(appId, collectionName, document, customHelper.getAccessList(req), isMasterKey); }).then(function(result) { res.json(result); }, function(error) { res.status(400).send(error); }); - - global.apiTracker.log(appId,"Object / Delete", req.url,sdk); - + + global.apiTracker.log(appId, "Object / Delete", req.url, sdk); + } }; @@ -90,16 +86,16 @@ function _getData(req, res) { //get document(s) object based on query and variou var skip = req.body.skip; var appKey = req.body.key || req.param('key'); var sdk = req.body.sdk || "REST"; - - global.appService.isMasterKey(appId, appKey).then(function (isMasterKey) { + + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { return global.customService.find(appId, collectionName, query, select, sort, limit, skip, customHelper.getAccessList(req), isMasterKey); - }).then(function (results) { + }).then(function(results) { res.json(results); - }, function (error) { + }, function(error) { res.status(400).send(error); }); - - global.apiTracker.log(appId,"Object / Find", req.url,sdk); + + global.apiTracker.log(appId, "Object / Find", req.url, sdk); } function _count(req, res) { //get document(s) object based on query and various parameters @@ -111,16 +107,16 @@ function _count(req, res) { //get document(s) object based on query and various var skip = req.body.skip; var appKey = req.body.key || req.param('key'); var sdk = req.body.sdk || "REST"; - - global.appService.isMasterKey(appId, appKey).then(function (isMasterKey) { + + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { return global.customService.count(appId, collectionName, query, limit, skip, customHelper.getAccessList(req), isMasterKey); - }).then(function (result) { + }).then(function(result) { res.json(result); - }, function (error) { + }, function(error) { res.status(400).send(error); }); - - global.apiTracker.log(appId,"Object / Count", req.url,sdk); + + global.apiTracker.log(appId, "Object / Count", req.url, sdk); } function _distinct(req, res, next) { //get document(s) object based on query and various parameters @@ -135,16 +131,16 @@ function _distinct(req, res, next) { //get document(s) object based on query and var skip = req.body.skip; var appKey = req.body.key || req.param('key'); var sdk = req.body.sdk || "REST"; - - global.appService.isMasterKey(appId, appKey).then(function (isMasterKey) { + + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { return global.customService.distinct(appId, collectionName, onKey, query, select, sort, limit, skip, customHelper.getAccessList(req), isMasterKey); - }).then(function (results) { + }).then(function(results) { res.json(results); - }, function (error) { + }, function(error) { res.status(400).send(error); }); - - global.apiTracker.log(appId,"Object / Distinct", req.url,sdk); + + global.apiTracker.log(appId, "Object / Distinct", req.url, sdk); } function _findOne(req, res) { //get a single document matching the search query @@ -157,14 +153,14 @@ function _findOne(req, res) { //get a single document matching the search query var skip = req.body.skip; var appKey = req.body.key || req.param('key'); var sdk = req.body.sdk || "REST"; - - global.appService.isMasterKey(appId, appKey).then(function (isMasterKey) { + + global.appService.isMasterKey(appId, appKey).then(function(isMasterKey) { return global.customService.findOne(appId, collectionName, query, select, sort, skip, customHelper.getAccessList(req), isMasterKey); - }).then(function (result) { + }).then(function(result) { res.json(result); - }, function (error) { + }, function(error) { res.status(400).send(error); }); - - global.apiTracker.log(appId,"Object / FindOne", req.url,sdk); + + global.apiTracker.log(appId, "Object / FindOne", req.url, sdk); } diff --git a/database-connect/realTime.js b/database-connect/realTime.js index 84a89d87..3ee1f6e3 100644 --- a/database-connect/realTime.js +++ b/database-connect/realTime.js @@ -153,7 +153,8 @@ module.exports = function(io) { }); - g.sendObjectNotification = function(appId, document, eventType) { + g.sendObjectNotification = function(appId, document, eventType, isMasterKey) { + //pass masterkey to access events as default ACL for event R/W is set to false try { //event type can be created, updated, deleted. if (document && document._tableName) { @@ -171,13 +172,13 @@ module.exports = function(io) { if (typeof sockets === "object") { for (var key in sockets) { if (sockets[key]) { - promises.push(_sendNotification(appId, document, sockets[key], eventType)); + promises.push(_sendNotification(appId, document, sockets[key], eventType, isMasterKey)); } } } else { for (var i = 0; i < sockets.length; i++) { var socket = sockets[i]; - promises.push(_sendNotification(appId, document, socket, eventType)); + promises.push(_sendNotification(appId, document, socket, eventType, isMasterKey)); } } @@ -202,14 +203,15 @@ module.exports = function(io) { /** */ -function _sendNotification(appId, document, socket, eventType) { +function _sendNotification(appId, document, socket, eventType, isMasterKey) { + //pass masterkey to access events as default ACL for event R/W is set to false var deferred = global.q.defer(); try { global.socketSessionHelper.getSession(socket.id, function(err, session) { if (err) { deferred.reject(); } - if (!session || global.aclHelper.isAllowedReadAccess(session.userId, session.roles, document.ACL)) { + if (!session || global.aclHelper.isAllowedReadAccess(session.userId, session.roles, document.ACL) || isMasterKey) { global.socketQueryHelper.getData(socket.id, eventType, function(err, socketData) { var socketQueryValidate = true; if (socketData && socketData.query) @@ -219,8 +221,9 @@ function _sendNotification(appId, document, socket, eventType) { socketData = { timestamp: '' }; + console.log(appId.toLowerCase() + 'table' + document._tableName.toLowerCase() + eventType.toLowerCase() + socketData.timestamp) socket.emit(appId.toLowerCase() + 'table' + document._tableName.toLowerCase() + eventType.toLowerCase() + socketData.timestamp, JSON.stringify(document)); - console.log("Socket Emitteddddddddddddddddddddddddddddddddddddd.", document); + console.log("Socket Emited.", document); } else { console.log('Socket Query doesn\'t satsfies the current document'); } diff --git a/databases/mongo.js b/databases/mongo.js index 393bbe80..9aa1d9eb 100644 --- a/databases/mongo.js +++ b/databases/mongo.js @@ -295,6 +295,13 @@ module.exports = function() { if (!sort) { sort = {}; } + //default sort added + /* + without sort if limit and skip are used, the records are returned out of order. To solve this default sort in ascending order of 'createdAt' is added + */ + + if (!sort['createdAt']) + sort['createdAt'] = 1 if (!limit || limit === -1) { limit = 20; diff --git a/helpers/cloudTable.js b/helpers/cloudTable.js index 972feee3..1e402c04 100644 --- a/helpers/cloudTable.js +++ b/helpers/cloudTable.js @@ -580,8 +580,97 @@ module.exports = { "editableByMasterKey": false } ], - Custom: [ - { + _Funnel: [ + { + "name": "id", + "_type": "column", + "dataType": "Id", + "required": true, + "unique": true, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false, + "defaultValue": null + }, { + "name": "updatedAt", + "_type": "column", + "dataType": "DateTime", + "required": true, + "unique": false, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false + }, { + "name": "createdAt", + "_type": "column", + "dataType": "DateTime", + "required": true, + "unique": false, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false + }, { + "name": "expires", + "_type": "column", + "dataType": "DateTime", + "required": false, + "unique": false, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false + }, { + "name": "ACL", + "_type": "column", + "dataType": "ACL", + "required": true, + "unique": false, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false + }, { + "name": "name", + "_type": "column", + "dataType": "Text", + "required": true, + "unique": false, + "relatedTo": "Text", + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false, + "defaultValue": null + }, { + "name": "data", + "_type": "column", + "dataType": "Object", + "required": true, + "unique": false, + "relatedTo": null, + "relationType": null, + "isDeletable": false, + "isEditable": false, + "isRenamable": false, + "editableByMasterKey": false + } + ], + Custom: [ + { name: 'id', _type: 'column', dataType: 'Id', @@ -593,9 +682,8 @@ module.exports = { isEditable: false, isRenamable: false, editableByMasterKey: false, - defaultValue: null - }, - { + defaultValue: null + }, { name: 'expires', _type: 'column', dataType: 'DateTime', @@ -607,9 +695,8 @@ module.exports = { isEditable: false, isRenamable: false, editableByMasterKey: false, - defaultValue: null - }, - { + defaultValue: null + }, { name: 'updatedAt', _type: 'column', dataType: 'DateTime', @@ -622,8 +709,7 @@ module.exports = { isRenamable: false, editableByMasterKey: false, defaultValue: null - }, - { + }, { name: 'createdAt', _type: 'column', dataType: 'DateTime', @@ -635,9 +721,8 @@ module.exports = { isEditable: false, isRenamable: false, editableByMasterKey: false, - defaultValue: null - }, - { + defaultValue: null + }, { name: 'ACL', _type: 'column', dataType: 'ACL', @@ -650,6 +735,6 @@ module.exports = { isRenamable: false, editableByMasterKey: false, defaultValue: null - } + } ] -}; \ No newline at end of file +}; diff --git a/services/app.js b/services/app.js index be06ebe0..1cd6985d 100644 --- a/services/app.js +++ b/services/app.js @@ -14,7 +14,7 @@ var util = require('../helpers/util.js'); var tablesData = require('../helpers/cloudTable'); var json2csv = require('json2csv'); var jsonToXlsx = require('json2xlsx'); -var jsonXlsxWriteFile = require('icg-json-to-xlsx'); +var jsonXlsxWriteFile = require('icg-json-to-xlsx'); var fs = require('fs'); module.exports = function() { @@ -456,7 +456,7 @@ module.exports = function() { if (project.keys.master === key) { deferred.resolve(true); } else { - if (project.keys.js === key){ + if (project.keys.js === key) { deferred.resolve(false); } else { deferred.resolve(false); @@ -502,36 +502,42 @@ module.exports = function() { return deferred.promise; }, - isClientAuthorized : function(appId,appKey,level,table){ + isClientAuthorized: function(appId, appKey, level, table) { var deferred = q.defer(); var self = this - self.isKeyValid(appId, appKey).then(function(isValidKey){ - if(isValidKey){ + self.isKeyValid(appId, appKey).then(function(isValidKey) { + if (isValidKey) { self.isMasterKey(appId, appKey).then(function(isMasterKey) { // resolve if masterKey - if(isMasterKey){ + if (isMasterKey) { deferred.resolve(true) } else { // else check with client keys acc to auth level // levels = table level or app level // for app level check in app settings , for table level check in table schema - if(level === 'table'){ - if(table) { + if (level === 'table') { + if (table) { deferred.resolve(!!table.isEditableByClientKey) - } else deferred.resolve(false); - } else { - self.getAllSettings(appId).then(function(settings){ - if(settings){ + } else + deferred.resolve(false); + } + else { + self.getAllSettings(appId).then(function(settings) { + if (settings) { // check for clientkey flag in genral settings - let generalSetting = settings.filter((function(x){ + let generalSetting = settings.filter((function(x) { return x.category === 'general' })) - if(generalSetting[0]){ + if (generalSetting[0]) { deferred.resolve(!!generalSetting[0].settings.isTableEditableByClientKey) - } else deferred.resolve(false); - } else deferred.resolve(false); - - }, function(error) { + } else + deferred.resolve(false); + } + else + deferred.resolve(false); + + } + , function(error) { deferred.reject(error); }); } @@ -542,7 +548,7 @@ module.exports = function() { } else { deferred.reject('Unauthorized'); } - },function(err){ + }, function(err) { deferred.reject(err); }) @@ -552,8 +558,10 @@ module.exports = function() { upsertTable: function(appId, tableName, schema, tableProps) { var deferred = global.q.defer(); - tableProps = tableProps || { isEditableByClientKey : false } - + tableProps = tableProps || { + isEditableByClientKey: false + } + try { var self = this; @@ -583,11 +591,13 @@ module.exports = function() { tableType = "file"; } else if (tableName === "_Event") { tableType = "event"; + } else if (tableName === "_Funnel") { + tableType = "funnel"; } else { tableType = "custom"; } - if (tableType === 'user' || tableType === 'role' || tableType === 'device' || tableType === 'file' || tableType === 'event') { + if (tableType === 'user' || tableType === 'role' || tableType === 'device' || tableType === 'file' || tableType === 'event' || tableType === 'funnel') { maxCount = 1; } else { maxCount = 99999; @@ -794,7 +804,8 @@ module.exports = function() { global.appService.upsertTable(appId, 'Device', tablesData.Device), global.appService.upsertTable(appId, 'User', tablesData.User), global.appService.upsertTable(appId, '_File', tablesData._File), - global.appService.upsertTable(appId, '_Event', tablesData._Event) + global.appService.upsertTable(appId, '_Event', tablesData._Event), + global.appService.upsertTable(appId, '_Funnel', tablesData._Funnel) ]); }, @@ -1027,42 +1038,40 @@ module.exports = function() { return deferred.promise; }, - exportTable : function(appId,tableName,exportType,isMasterKey,accessList){ + exportTable: function(appId, tableName, exportType, isMasterKey, accessList) { var deferred = q.defer(); - global.customService.find(appId, tableName,{},null, null, null, null, accessList, isMasterKey).then(function(tables){ - - if(exportType === 'csv') - { - var result = json2csv({ data: tables}); - deferred.resolve(result); - }else if(exportType === 'xlsx' || exportType === 'xls') - { - var random = util.getId(); - var fileName = '/tmp/tempfile'+random+'.xlsx'; - var converted = convertObjectToString(tables); - var outputFile = jsonXlsxWriteFile.writeFile(fileName, converted); + global.customService.find(appId, tableName, {}, null, null, null, null, accessList, isMasterKey).then(function(tables) { + + if (exportType === 'csv') { + var result = json2csv({data: tables}); + deferred.resolve(result); + } else if (exportType === 'xlsx' || exportType === 'xls') { + var random = util.getId(); + var fileName = '/tmp/tempfile' + random + '.xlsx'; + var converted = convertObjectToString(tables); + var outputFile = jsonXlsxWriteFile.writeFile(fileName, converted); fs.readFile(fileName, function read(err, data) { if (err) { - deferred.reject("Error : Failed to convert the table."); + deferred.reject("Error : Failed to convert the table."); } - fs.unlink(fileName,function(err){ - if(err){ + fs.unlink(fileName, function(err) { + if (err) { deferred.reject(err); - } + } deferred.resolve(data); - }); + }); }); - }else if(exportType === 'json'){ + } else if (exportType === 'json') { deferred.resolve(tables); - } else{ + } else { deferred.reject('Invalid exportType ,exportType should be csv,xls,xlsx,json') } - },function(err){ + }, function(err) { deferred.reject(err); }); - return deferred.promise; + return deferred.promise; } }; }; @@ -1137,6 +1146,8 @@ function _getDefaultColumnList(type) { defaultColumn.concat(['name', 'contentType', 'path', 'url', 'size']); } else if (type == 'event') { defaultColumn.concat(['user', 'type', 'name', 'data']); + } else if (type == 'funnel') { + defaultColumn.concat(['name', 'data']); } return defaultColumn; @@ -1243,7 +1254,7 @@ function _checkValidDataType(columns, deafultDataType, tableType) { return false; } //name for event table - if (key === 'name' && tableType === 'event') { + if (key === 'name' && (tableType === 'event' || tableType === 'funnel')) { if (columns[index].relationType != null || columns[index].required != true || columns[index].unique != false || columns[index].dataType != 'Text') return false; } @@ -1367,8 +1378,7 @@ function _checkValidDataType(columns, deafultDataType, tableType) { } } else if (columns[i].dataType === 'Email') { if (columns[i].defaultValue.match(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)[0] !== columns[i].defaultValue) { - return - false; // if the set dataType is not other string Datatypes (Text, EncryptedText, DateTime) available in cloudboost; + return false; // if the set dataType is not other string Datatypes (Text, EncryptedText, DateTime) available in cloudboost; } } else if (['Text', 'EncryptedText', 'DateTime'].indexOf(columns[i].dataType) === -1) { return false; @@ -1477,6 +1487,9 @@ function _getDefaultColumnWithDataType(type) { defaultColumn['type'] = 'Text'; defaultColumn['name'] = 'Text'; defaultColumn['data'] = 'Object'; + } else if (type == 'funnel') { + defaultColumn['name'] = 'Text'; + defaultColumn['data'] = 'Object'; } return defaultColumn; @@ -1513,18 +1526,14 @@ function deleteAppFromRedis(appId) { return deferred.promise; } -function convertObjectToString(arr) -{ - for(let j in arr) - { +function convertObjectToString(arr) { + for (let j in arr) { let data = arr[j]; - for(let i in data) - { - if(typeof data[i] == 'object') - { - data[i] = JSON.stringify(data[i]); + for (let i in data) { + if (typeof data[i] == 'object') { + data[i] = JSON.stringify(data[i]); } - + } } return arr; diff --git a/services/cloudObjects.js b/services/cloudObjects.js index 9ca2a2fd..8c04f87c 100644 --- a/services/cloudObjects.js +++ b/services/cloudObjects.js @@ -28,7 +28,7 @@ module.exports = function() { }); } else { - _encryptPasswordInQuery(appId, collectionName, query).then(function(query) { + _modifyFieldsInQuery(appId, collectionName, query).then(function(query) { databaseDriver.find(appId, collectionName, query, select, sort, limit, skip, accessList, isMasterKey).then(function(doc) { deferred.resolve(doc); }, function(err) { @@ -54,7 +54,7 @@ module.exports = function() { var deferred = q.defer(); try { - _encryptPasswordInQuery(appId, collectionName, query).then(function(query) { + _modifyFieldsInQuery(appId, collectionName, query).then(function(query) { databaseDriver.count(appId, collectionName, query, limit, skip, accessList, isMasterKey).then(function(doc) { deferred.resolve(doc); }, function(err) { @@ -77,7 +77,7 @@ module.exports = function() { var deferred = q.defer(); try { - _encryptPasswordInQuery(appId, collectionName, query).then(function(query) { + _modifyFieldsInQuery(appId, collectionName, query).then(function(query) { databaseDriver.distinct(appId, collectionName, onKey, query, select, sort, limit, skip, accessList, isMasterKey).then(function(doc) { deferred.resolve(doc); }, function(err) { @@ -101,7 +101,7 @@ module.exports = function() { var deferred = q.defer(); try { - _encryptPasswordInQuery(appId, collectionName, query).then(function(query) { + _modifyFieldsInQuery(appId, collectionName, query).then(function(query) { databaseDriver.findOne(appId, collectionName, query, select, sort, skip, accessList, isMasterKey).then(function(doc) { deferred.resolve(doc); }, function(err) { @@ -291,7 +291,8 @@ function _save(appId, collectionName, document, accessList, isMasterKey, reqType promises.push(databaseDriver.save(appId, mongoDocs)); global.q.allSettled(promises).then(function(array) { if (array[0].state === 'fulfilled') { - _sendNotification(appId, array[0], reqType); + //pass masterkey to access events as default ACL for event R/W is set to false + _sendNotification(appId, array[0], reqType, isMasterKey); unModDoc = _merge(parentId, array[0].value, unModDoc); console.log('SAVED Doc'); console.log(unModDoc); @@ -390,14 +391,16 @@ function _validateSchema(appId, listOfDocs, accessList, isMasterKey) { return deferred.promise; } -function _sendNotification(appId, res, reqType) { +function _sendNotification(appId, res, reqType, isMasterKey) { + //pass masterkey to access events as default ACL for event R/W is set to false + try { for (var i = 0; i < res.value.length; i++) { if (res.value[i].state === 'fulfilled') { if (reqType.save.indexOf(res.value[i].value._id) >= 0) { - global.realTime.sendObjectNotification(appId, res.value[i].value, 'created'); + global.realTime.sendObjectNotification(appId, res.value[i].value, 'created', isMasterKey); } else { - global.realTime.sendObjectNotification(appId, res.value[i].value, 'updated'); + global.realTime.sendObjectNotification(appId, res.value[i].value, 'updated', isMasterKey); } } } @@ -525,28 +528,27 @@ var _isSchemaValid = function(appId, collectionName, document, accessList, isMas // if column does not exist create a new column if (!col) { columnNotFound = true - try{ + try { let detectedDataType = type.inferDataType(document[key]); let newCol = { - name:key, - _type:"column", - dataType:detectedDataType, - defaultValue:null, - editableByMasterKey:false, - isDeletable:true, - isEditable:true, - isRenamable:false, - relatedTo: type.inferRelatedToType(detectedDataType, document[key]), - relationType:null, - required:false, - unique:false + name: key, + _type: "column", + dataType: detectedDataType, + defaultValue: null, + editableByMasterKey: false, + isDeletable: true, + isEditable: true, + isRenamable: false, + relatedTo: type.inferRelatedToType(detectedDataType, document[key]), + relationType: null, + required: false, + unique: false }; //push the new column to the old schema table.columns.push(newCol); - } - catch (err) { + } catch (err) { global.winston.log('error', { "error": String(err), "stack": new Error().stack @@ -641,34 +643,29 @@ var _isSchemaValid = function(appId, collectionName, document, accessList, isMas } } } - if(columnNotFound){ + if (columnNotFound) { // update the table schema var createNewColumnPromise = q.defer(); var schemaCursor = global.mongoClient.db(appId).collection("_Schema"); - schemaCursor.findOneAndUpdate( - { - name: document._tableName - }, - { - $set: table - }, - { - upsert: true, - returnOriginal: false - }, - function(err, response) { - var table = null; - if (response && response.value) - table = response.value; - - if (err) { - createNewColumnPromise.reject("Error : Failed to update the table with the new column. "); - } else if (table) { - createNewColumnPromise.resolve(); - console.log("Column " + key + " created."); - } + schemaCursor.findOneAndUpdate({ + name: document._tableName + }, { + $set: table + }, { + upsert: true, + returnOriginal: false + }, function(err, response) { + var table = null; + if (response && response.value) + table = response.value; + + if (err) { + createNewColumnPromise.reject("Error : Failed to update the table with the new column. "); + } else if (table) { + createNewColumnPromise.resolve(); + console.log("Column " + key + " created."); } - ) + }) promises.push(createNewColumnPromise.promise) } @@ -722,6 +719,8 @@ function _getTableType(tableName) { tableType = "file"; } else if (tableName === "_Event") { tableType = "event"; + } else if (tableName === "_Funnel") { + tableType = "funnel"; } return tableType; } @@ -1291,8 +1290,8 @@ function _getSchema(appId, collectionName) { return deferred.promise; } -//this function encrypts the password, if the password is passed in the Query. -function _encryptPasswordInQuery(appId, collectionName, query) { +//this function modifies the fields ['password','datetime'] passed in the Query. +function _modifyFieldsInQuery(appId, collectionName, query) { var deferred = global.q.defer(); @@ -1302,19 +1301,27 @@ function _encryptPasswordInQuery(appId, collectionName, query) { } else { _getSchema(appId, collectionName).then(function(columns) { var passwordColumnNames = []; + var dateTimeColumnNames = []; + // push in fields to be modified / i.e DateTime and Encypted fields for (var i = 0; i < columns.length; i++) { if (columns[i].dataType === 'EncryptedText') { passwordColumnNames.push(columns[i].name); } + if (columns[i].dataType === 'DateTime') { + dateTimeColumnNames.push(columns[i].name); + } } - //resolve if there are no password fields. - if (passwordColumnNames.length === 0) { + //resolve if there are no password fields or DateTime fields + if (passwordColumnNames.length === 0 && dateTimeColumnNames === 0) { deferred.resolve(query); } else { //or modify the query and resolve it. - query = _recursiveEncryptQuery(query, passwordColumnNames); + if (passwordColumnNames.length) + query = _recursiveModifyQuery(query, passwordColumnNames, 'encrypt'); + if (dateTimeColumnNames.length) + query = _recursiveModifyQuery(query, dateTimeColumnNames, 'datetime'); deferred.resolve(query); } @@ -1345,19 +1352,33 @@ function _encrypt(data) { } } -function _recursiveEncryptQuery(query, passwordColumnNames) { +function _recursiveModifyQuery(query, columnNames, type) { for (var key in query) { if (key === '$or') { for (var i = 0; i < query[key].length; i++) { - query[key][i] = _recursiveEncryptQuery(query[key][i], passwordColumnNames); + query[key][i] = _recursiveModifyQuery(query[key][i], columnNames, type); } } } return _.mapObject(query, function(val, key) { - if (passwordColumnNames.indexOf(key) > -1) { + if (columnNames.indexOf(key) > -1) { if (typeof val !== 'object') { - return _encrypt(val); + if (type === 'encrypt') { + return _encrypt(val); + } + } else { + // for datetime fields convert them to a fomat which mongodb can query + if (type === 'datetime') { + try { + Object.keys(val).map(function(x) { + val[x] = new Date(val[x]); + }) + return val; + } catch (e) { + return val; + } + } } } return val;