diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules diff --git a/Dockerfile b/Dockerfile index 8070c85..f49610a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14-stretch-slim +FROM node:16-stretch-slim RUN apt-get update && apt-get upgrade -y && apt-get install -y git build-essential python3 RUN mkdir /src COPY . /src diff --git a/README.md b/README.md index a89dc0e..cf84b4e 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,14 @@ userFilter -- list of values which must be present in given field in filtered da \*\* -- immune to filters (can see everything) Public -- users with no userFilter are assigned this filter An item with no filter value is returned in all cases, and is thus also public. + +## Local Development Environment +In order to quickly setup a development environment, make use of the `setup_script.sh` script. This will setup the project, initialize and seed the database configurations, import routes and initialize environment config files and generate the keys required. + +First clone the Caracal (backup-dev branch), caMicroscope and the Distro repositories and make sure that all of them are in the same parent directory. + +Run the script with `./setup_script` or `bash ./setup_script.sh` + +The script is configured to load a database named "`camic`" from server at "`127.0.0.1`". In order to specify different name and host, simply pass the two while calling the script, like `./setup_script custom_host custom_database_name` + +Run `npm start` to start the application and see it running at `localhost:4010` diff --git a/caracal.js b/caracal.js index 5d829c8..8516348 100644 --- a/caracal.js +++ b/caracal.js @@ -24,6 +24,8 @@ const Model = require('./handlers/modelTrainer.js'); const DataTransformationHandler = require('./handlers/dataTransformationHandler.js'); // TODO validation of data +const {connector} = require("./service/database/connector"); + var WORKERS = process.env.NUM_THREADS || 4; var PORT = process.env.PORT || 4010; @@ -196,7 +198,14 @@ var startApp = function(app) { throng(WORKERS, startApp(app)); -const handler = new DataTransformationHandler(MONGO_URI, './json/configuration.json'); -handler.startHandler(); +/** initialize DataTransformationHandler only after database is ready */ +connector.init().then(() => { + const handler = new DataTransformationHandler(MONGO_URI, './json/configuration.json'); + handler.startHandler(); +}).catch((e) => { + console.error("error connecting to database"); + process.exit(1); +}); module.exports = app; // for tests + diff --git a/handlers/authHandlers.js b/handlers/authHandlers.js index 0db87ae..c5c470f 100644 --- a/handlers/authHandlers.js +++ b/handlers/authHandlers.js @@ -45,13 +45,13 @@ try { } try { - const prikeyPath = './keys/key.pub'; - if (fs.existsSync(prikeyPath)) { - var PUBKEY = fs.readFileSync(prikeyPath, 'utf8'); + const pubkeyPath = './keys/key.pub'; + if (fs.existsSync(pubkeyPath)) { + var PUBKEY = fs.readFileSync(pubkeyPath, 'utf8'); } else { if (DISABLE_SEC || ENABLE_SECURITY_AT && Date.parse(ENABLE_SECURITY_AT) > Date.now()) { PUBKEY = ''; - console.warn('pubkey null since DISABLE_SEC and no prikey provided'); + console.warn('pubkey null since DISABLE_SEC and no pubkey provided'); } else { console.error('pubkey does not exist'); } diff --git a/handlers/dataHandlers.js b/handlers/dataHandlers.js index 7852339..b89b5af 100644 --- a/handlers/dataHandlers.js +++ b/handlers/dataHandlers.js @@ -1,185 +1,11 @@ -var mongo = require('mongodb'); - -var MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost'; -var DISABLE_SEC = (process.env.DISABLE_SEC === 'true') || false; - - -function mongoFind(database, collection, query) { - return new Promise(function(res, rej) { - try { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - if (err) { - rej(err); - } else { - if (query['_id']) { - query['_id'] = new mongo.ObjectID(query['_id']); - } - var dbo = db.db(database); - dbo.collection(collection).find(query).toArray(function(err, result) { - if (err) { - rej(err); - } - // compatible wiht bindaas odd format - result.forEach((x) => { - x['_id'] = { - '$oid': x['_id'], - }; - }); - res(result); - db.close(); - }); - } - }); - } catch (error) { - rej(error); - } - }); -} - -function mongoDistinct(database, collection, upon, query) { - return new Promise(function(res, rej) { - try { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - if (err) { - rej(err); - } else { - var dbo = db.db(database); - dbo.collection(collection).distinct(upon, query, function(err, result) { - if (err) { - rej(err); - } - res(result); - db.close(); - }); - } - }); - } catch (error) { - console.error(error); - rej(error); - } - }); -} - -function mongoAdd(database, collection, data) { - return new Promise(function(res, rej) { - // if data not array, make it one - if (!Array.isArray(data)) { - data = [data]; - } - try { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - if (err) { - rej(err); - } else { - var dbo = db.db(database); - dbo.collection(collection).insertMany(data, function(err, result) { - if (err) { - rej(err); - } - res(result); - db.close(); - }); - } - }); - } catch (error) { - console.error(error); - rej(error); - } - }); -} - -function mongoDelete(database, collection, query) { - return new Promise(function(res, rej) { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - try { - if (err) { - rej(err); - } else { - var dbo = db.db(database); - if (query['_id']) { - query['_id'] = new mongo.ObjectID(query['_id']); - } - dbo.collection(collection).deleteOne(query, function(err, result) { - if (err) { - rej(err); - } - delete result.connection; - res(result); - db.close(); - }); - } - } catch (error) { - console.error(error); - rej(error); - } - }); - }); -} - -function mongoAggregate(database, collection, pipeline) { - return new Promise(function(res, rej) { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - try { - if (err) { - rej(err); - } else { - var dbo = db.db(database); - dbo.collection(collection).aggregate(pipeline).toArray(function(err, result) { - if (err) { - rej(err); - } - // compatible wiht bindaas odd format - // result.forEach((x) => { - // x['_id'] = { - // '$oid': x['_id'], - // }; - // }); - res(result); - db.close(); - }); - } - } catch (error) { - console.error(error); - rej(error); - } - }); - }); -} - -function mongoUpdate(database, collection, query, newVals) { - return new Promise(function(res, rej) { - try { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - if (err) { - rej(err); - } else { - var dbo = db.db(database); - if (query['_id']) { - query['_id'] = new mongo.ObjectID(query['_id']); - } - dbo.collection(collection).updateOne(query, newVals, function(err, result) { - if (err) { - console.log(err); - rej(err); - } - delete result.connection; - res(result); - db.close(); - }); - } - }); - } catch (error) { - rej(error); - } - }); -} +const DISABLE_SEC = (process.env.DISABLE_SEC === 'true') || false; +const mongoDB = require("../service/database"); var General = {}; General.find = function(db, collection) { return function(req, res, next) { var query = req.query; - delete query.token; - mongoFind(db, collection, query).then((x) => { + mongoDB.find(db, collection, query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -190,7 +16,7 @@ General.get = function(db, collection) { return function(req, res, next) { var query = req.query; delete query.token; - mongoFind(db, collection, {_id: req.query.id}).then((x) => { + mongoDB.find(db, collection, {_id: req.query.id}).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -201,7 +27,7 @@ General.distinct = function(db, collection, upon) { return function(req, res, next) { var query = req.query; delete query.token; - mongoDistinct(db, collection, upon, query).then((x) => { + mongoDB.distinct(db, collection, upon, query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -211,7 +37,7 @@ General.distinct = function(db, collection, upon) { General.add = function(db, collection) { return function(req, res, next) { var data = JSON.parse(req.body); - mongoAdd(db, collection, data).then((x) => { + mongoDB.add(db, collection, data).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -225,7 +51,7 @@ General.update = function(db, collection) { var newVals = { $set: JSON.parse(req.body), }; - mongoUpdate(db, collection, query, newVals).then((x) => { + mongoDB.update(db, collection, query, newVals).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -236,7 +62,7 @@ General.delete = function(db, collection) { return function(req, res, next) { var query = req.query; delete query.token; - mongoDelete(db, collection, query).then((x) => { + mongoDB.delete(db, collection, query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -249,7 +75,7 @@ Presetlabels.add = function(req, res, next) { var query = req.query; delete query.token; var labels = JSON.parse(req.body); - mongoUpdate('camic', 'configuration', {'config_name': 'preset_label'}, {$push: {configuration: labels}}).then((x) => { + mongoDB.update('camic', 'configuration', {'config_name': 'preset_label'}, {$push: {configuration: labels}}).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -287,7 +113,7 @@ Presetlabels.update = function(req, res, next) { newVals['$unset']['configuration.$.key'] = 1; } - mongoUpdate('camic', 'configuration', + mongoDB.update('camic', 'configuration', { 'config_name': 'preset_label', 'configuration.id': query.id, @@ -301,7 +127,7 @@ Presetlabels.update = function(req, res, next) { Presetlabels.remove = function(req, res, next) { var query = req.query; delete query.token; - mongoUpdate('camic', 'configuration', + mongoDB.update('camic', 'configuration', { 'config_name': 'preset_label', }, {$pull: {configuration: {id: query.id}}}).then((x) => { @@ -337,7 +163,7 @@ Mark.spatial = function(req, res, next) { '$gt': parseFloat(query.footprint), }; } - mongoFind('camic', 'mark', query).then((x) => { + mongoDB.find('camic', 'mark', query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -383,7 +209,7 @@ Mark.multi = function(req, res, next) { }; } - mongoFind('camic', 'mark', query).then((x) => { + mongoDB.find('camic', 'mark', query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -415,12 +241,12 @@ Mark.findMarkTypes = function(req, res, next) { }, }, ]; - mongoAggregate('camic', 'mark', pipeline).then((x) => { + mongoDB.aggregate('camic', 'mark', pipeline).then((x) => { req.data = x; next(); }).catch((e) => next(e)); } else { - mongoDistinct('camic', 'mark', 'provenance.analysis', query).then((x) => { + mongoDB.distinct('camic', 'mark', 'provenance.analysis', query).then((x) => { req.data = x; next(); }).catch((e) => next(e)); @@ -431,7 +257,7 @@ var Heatmap = {}; Heatmap.types = function(req, res, next) { var query = req.query; delete query.token; - mongoFind('camic', 'heatmap', query, { + mongoDB.find('camic', 'heatmap', query, { 'data': 0, }).then((x) => { x.forEach((x)=>delete x.data); @@ -443,7 +269,7 @@ Heatmap.types = function(req, res, next) { var User = {}; User.forLogin = function(email) { - return mongoFind('camic', 'user', {'email': email}); + return mongoDB.find('camic', 'user', {'email': email}); }; User.wcido = function(req, res, next) { diff --git a/handlers/dataTransformationHandler.js b/handlers/dataTransformationHandler.js index 3458777..7f09a07 100644 --- a/handlers/dataTransformationHandler.js +++ b/handlers/dataTransformationHandler.js @@ -1,5 +1,5 @@ -const client = require("mongodb").MongoClient; const fs = require("fs"); +const mongoDB = require("../service/database"); class DataTransformationHandler { constructor(url, path) { @@ -37,7 +37,7 @@ class DataTransformationHandler { return config; } - connect() { + async connect() { if (this.isProcessing) { return; } @@ -45,88 +45,48 @@ class DataTransformationHandler { this.cleanHandler(); return; } + this.isProcessing = true; const query = {config_name: "preset_label"}; - client - .connect(this.url, {useNewUrlParser: true}) - .then((dbc) => { - const collection = dbc.db("camic").collection("configuration"); - collection.find(query).toArray((err, rs) => { - if (err) { - this.isProcessing = false; - console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); - console.log(err); - dbc.close(); - this.cleanHandler(); - return; - } - // add default data - if (rs.length < 1) { - // read default data from json - const defaultData = this.loadDefaultData(); - // insert default data - collection.insertOne(defaultData, (err, result) => { - if (err) { - this.isProcessing = false; - console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); - console.log(err); - dbc.close(); - this.cleanHandler(); - return; - } - dbc.close(); - // clear handler - this.cleanHandler(); - }); - return; - } + try { + /** fetch saved configurations */ + const rs = await mongoDB.find("camic", "configuration", query, false); + + /** read default data and write to database */ + if (rs.length < 1) { + const defaultData = this.loadDefaultData(); + await mongoDB.add("camic", "configuration", defaultData); + } - if (rs.length > 0 && rs[0].version != "1.0.0") { - const config = rs[0]; - const list = []; - if (config.configuration && Array.isArray(config.configuration)) { - config.configuration.forEach((node) => { - this.extractNode(node, list); - }); - } - config.configuration = list; - config.version = '1.0.0'; - if (!(list && list.length)) return; - collection.deleteOne(query, (err, result) => { - if (err) { - this.isProcessing = false; - console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); - console.log(err); - dbc.close(); - this.cleanHandler(); - return; - } - collection.insertOne(config, (err, result) => { - if (err) { - this.isProcessing = false; - console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); - console.log(err); - dbc.close(); - this.cleanHandler(); - return; - } - this.isProcessing = false; - console.log(`||-- The Document At The 'configuration' Collection Has Been Upgraded To Version 1.0.0 --||`); - dbc.close(); - this.cleanHandler(); - }); - }); - } + /** if not default configuration */ + if (rs.length > 0 && rs[0].version != "1.0.0") { + const config = rs[0]; + const list = []; + if (config.configuration && Array.isArray(config.configuration)) { + config.configuration.forEach((node) => { + this.extractNode(node, list); }); - }).catch((err) => { - console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); - console.log(err.message); - this.isProcessing = false; - if (this.max == this.counter) { - this.cleanHandler(); - } - }); + } + + config.configuration = list; + config.version = "1.0.0"; + if (!(list && list.length)) { + return; + } + + /** delete old stored object and insert new one */ + await mongoDB.delete("camic", "configuration", query); + await mongoDB.add("camic", "configuration", config); + this.isProcessing = false; + this.cleanHandler(); + } + } catch (err) { + console.log(`||-- The 'Preset Labels' Document Upgrade Is Failed --||`); + console.log(err); + this.cleanHandler(); + } } + extractNode(node, list) { if (node.children && Array.isArray(node.children)) { node.children.forEach((n) => { diff --git a/handlers/monitorHandlers.js b/handlers/monitorHandlers.js index 05bf5ca..642b04a 100644 --- a/handlers/monitorHandlers.js +++ b/handlers/monitorHandlers.js @@ -1,29 +1,21 @@ -var mongo = require('mongodb'); +const {getConnection} = require("../service/database/connector"); -var MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost'; -// handle monitoring checks - -let monitor = {}; - -monitor.check = function(type) { - if (type == "basic") { - return new Promise(function(res, rej) { - let checkMsg = {"status": "up", "checkType": "basic"}; - res(checkMsg); - }); - } - if (type == "mongo") { - return new Promise(function(res, rej) { - mongo.MongoClient.connect(MONGO_URI, function(err, db) { - if (err) { - rej(err); - } else { - res({"status": "up", "checkType": "mongo"}); - } - }); - }); - } +/** + * @param {string} type Monitor to check status of database connection + * @returns {Promise<{status:string, checkType:string}>} status of service + */ +const check = function(type) { + return new Promise((resolve, reject) => { + if (type === "basic") { + resolve({status: "up", checkType: "basic"}); + } else if (type === "mongo") { + if (getConnection() === undefined) { + const error = new Error("Error connecting to database"); + reject(error); + } + resolve({status: "up", checkType: "mongo"}); + } + }); }; - -module.exports = monitor; +module.exports = {check}; diff --git a/package-lock.json b/package-lock.json index 53f5e04..64bdd1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,16 +31,16 @@ } }, "@tensorflow/tfjs": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-2.7.0.tgz", - "integrity": "sha512-LTYK6+emFweYa3zn/o511JUR6s14/yGZpoXvFSUtdwolYHI+J50r/CyYeFpvtoTD7uwcNFQhbBAtp4L4e3Hsaw==", - "requires": { - "@tensorflow/tfjs-backend-cpu": "2.7.0", - "@tensorflow/tfjs-backend-webgl": "2.7.0", - "@tensorflow/tfjs-converter": "2.7.0", - "@tensorflow/tfjs-core": "2.7.0", - "@tensorflow/tfjs-data": "2.7.0", - "@tensorflow/tfjs-layers": "2.7.0", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-2.8.6.tgz", + "integrity": "sha512-/Hk3YCAreNicuQJsAIG32UGHaQj8UwX8y8ZrKVb/CrXOhrRyZmxGSZt9KMVe8MDoydenuGhZCqJUIaWdIKIA5g==", + "requires": { + "@tensorflow/tfjs-backend-cpu": "2.8.6", + "@tensorflow/tfjs-backend-webgl": "2.8.6", + "@tensorflow/tfjs-converter": "2.8.6", + "@tensorflow/tfjs-core": "2.8.6", + "@tensorflow/tfjs-data": "2.8.6", + "@tensorflow/tfjs-layers": "2.8.6", "argparse": "^1.0.10", "chalk": "^4.1.0", "core-js": "3", @@ -57,9 +57,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -94,20 +94,20 @@ } }, "@tensorflow/tfjs-backend-cpu": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-2.7.0.tgz", - "integrity": "sha512-R6ORcWq3ub81ABvBZEZ8Ok5OOT59B4AsRe66ds7B/NK0nN+k6y37bR3ZDVjgkEKNWNvzB7ydODikge3GNmgQIQ==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-2.8.6.tgz", + "integrity": "sha512-x9WTTE9p3Pon2D0d6HH1UCIJsU1w3v9sF3vxJcp+YStrjDefWoW5pwxHCckEKTRra7GWg3CwMKK3Si2dat4H1A==", "requires": { "@types/seedrandom": "2.4.27", "seedrandom": "2.4.3" } }, "@tensorflow/tfjs-backend-webgl": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-2.7.0.tgz", - "integrity": "sha512-K7Rk5YTSWOZ969EZvh3w786daPn2ub4mA2JsX7mXKhBPUaOP9dKbBdLj9buCuMcu4zVq2pAp0QwpHSa4PHm3xg==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-2.8.6.tgz", + "integrity": "sha512-kPgm3Dim0Li5MleybYKSZVUCu91ipDjZtTA5RrJx/Dli115qwWdiRGOHYwsIEY61hZoE0m3amjWLUBxtwMW1Nw==", "requires": { - "@tensorflow/tfjs-backend-cpu": "2.7.0", + "@tensorflow/tfjs-backend-cpu": "2.8.6", "@types/offscreencanvas": "~2019.3.0", "@types/seedrandom": "2.4.27", "@types/webgl-ext": "0.0.30", @@ -116,14 +116,14 @@ } }, "@tensorflow/tfjs-converter": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-2.7.0.tgz", - "integrity": "sha512-SBpKYn/MkN8US7DeTcnvqHpvp/WKcwzpdgkQF+eHMHEbS1lXSlt4BHhOFgRdLPzy1gEC9+6P0VdTE8NQ737t/Q==" + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-2.8.6.tgz", + "integrity": "sha512-Uv4YC66qjVC9UwBxz0IeLZ8KS2CReh63WlGRtHcSwDEYiwsa7cvp9H6lFSSPT7kiJmrK6JtHeJGIVcTuNnSt9w==" }, "@tensorflow/tfjs-core": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-2.7.0.tgz", - "integrity": "sha512-4w5zjK6C5nkLatHpzARVQNd5QKtIocJRwjZIwWcScT9z2z1dX4rVmDoUpYg1cdD4H+yRRdI0awRaI3SL34yy8Q==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-2.8.6.tgz", + "integrity": "sha512-jS28M1POUOjnWgx3jp1v5D45DUQE8USsAHHkL/01z75KnYCAAmgqJSH4YKLiYACg3eBLWXH/KTcSc6dHAX7Kfg==", "requires": { "@types/offscreencanvas": "~2019.3.0", "@types/seedrandom": "2.4.27", @@ -133,26 +133,26 @@ } }, "@tensorflow/tfjs-data": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-2.7.0.tgz", - "integrity": "sha512-gsVklCwqqlxhykI7U2Uy5c2hjommQCAi+3y2/LER4TNtzQTzWaGKyIXvuLuL0tE896yuzXILIMZhkUjDmUiGxA==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-2.8.6.tgz", + "integrity": "sha512-zoDUfd5TfkYdviqu2bObwyJGXJiOvBckOTP9j36PUs6s+4DbTIDttyxdfeEaiiLX9ZUFU58CoW+3LI/dlFVyoQ==", "requires": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1" } }, "@tensorflow/tfjs-layers": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-2.7.0.tgz", - "integrity": "sha512-78zsD2LLrHQuDYv0EeV83LiF0M69lKsBfuTB3FIBgS85gapZPyHh4wooKda2Y4H9EtLogU+C6bArZuDo8PaX+g==" + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-2.8.6.tgz", + "integrity": "sha512-fdZ0i/R2dIKmy8OB5tBAsm5IbAHfJpI6AlbjxpgoU3aWj1HCdDo+pMji928MkDJhP01ISgFTgw/7PseGNaUflw==" }, "@tensorflow/tfjs-node": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-2.7.0.tgz", - "integrity": "sha512-0cWplm7AE40gi2llqoAp+lD/0X3dVJ8kb7Arrqb5lMhShRWUFZpULH+F0fJI6Yax4LBTzBi2SZKGL/O8krZsxg==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-2.8.6.tgz", + "integrity": "sha512-lq7fielTU42K8hyzvbmLTKzp4WqIAdgpWdbUPoHpXDCqTgWGb1UfMaDt/oxBkA7i9DFvyN3cem/tdeQRC95pLA==", "requires": { - "@tensorflow/tfjs": "2.7.0", - "@tensorflow/tfjs-core": "2.7.0", + "@tensorflow/tfjs": "2.8.6", + "@tensorflow/tfjs-core": "2.8.6", "adm-zip": "^0.4.11", "google-protobuf": "^3.9.2", "https-proxy-agent": "^2.2.1", @@ -203,12 +203,12 @@ "dev": true }, "@types/express": { - "version": "4.17.9", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz", - "integrity": "sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", + "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" } @@ -223,9 +223,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.17.tgz", - "integrity": "sha512-YYlVaCni5dnHc+bLZfY908IG1+x5xuibKZMGv8srKkvtul3wUuanYvpIj9GXXoWkQbaAdR+kgX46IETKUALWNQ==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz", + "integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -241,9 +241,9 @@ } }, "@types/mime": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { "version": "12.12.14", @@ -251,18 +251,18 @@ "integrity": "sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA==" }, "@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz", + "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==", "requires": { "@types/node": "*", "form-data": "^3.0.0" }, "dependencies": { "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -277,9 +277,9 @@ "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" }, "@types/qs": { - "version": "6.9.5", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", - "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" }, "@types/range-parser": { "version": "1.2.3", @@ -292,11 +292,11 @@ "integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE=" }, "@types/serve-static": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", - "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", "requires": { - "@types/mime": "*", + "@types/mime": "^1", "@types/node": "*" } }, @@ -415,9 +415,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -542,9 +542,9 @@ "dev": true }, "bson": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", - "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, "buffer-equal-constant-time": { "version": "1.0.1", @@ -569,16 +569,16 @@ "dev": true }, "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.1.0", + "pathval": "^1.1.1", "type-detect": "^4.0.5" } }, @@ -657,9 +657,9 @@ "dev": true }, "cliui": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.3.tgz", - "integrity": "sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -760,9 +760,9 @@ "dev": true }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.0.tgz", + "integrity": "sha512-SaMnchL//WwU2Ot1hhkPflE8gzo7uq1FGvUJ8GKmi3TOU7rGTHIU+eir1WGf6qOtTyxdfdcp10yPdGZ59sQ3hw==" }, "core-util-is": { "version": "1.0.2", @@ -827,9 +827,9 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" }, "depd": { "version": "1.1.2", @@ -862,9 +862,9 @@ } }, "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" }, "ecdsa-sig-formatter": { "version": "1.0.11", @@ -1385,9 +1385,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -1403,9 +1403,9 @@ } }, "google-protobuf": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.13.0.tgz", - "integrity": "sha512-ZIf3qfLFayVrPvAjeKKxO5FRF1/NwRxt6Dko+fWEMuHwHbZx8/fcaAao9b0wCM6kr8qeg2te8XTpyuvKuD9aKw==" + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.16.0.tgz", + "integrity": "sha512-gBY66yYL1wbQMU2r1POkXSXkm035Ni0wFv3vx0K9IEUsJLP9G5rAcFVn0xUXfZneRu6MmDjaw93pt/DE56VOyw==" }, "growl": { "version": "1.10.5", @@ -1431,9 +1431,9 @@ "dev": true }, "helmet": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.1.0.tgz", - "integrity": "sha512-KWy75fYN8hOG2Rhl8e5B3WhOzb0by1boQum85TiddIE9iu6gV+TXbUjVC17wfej0o/ZUpqB9kxM0NFCZRMzf+Q==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" }, "http-errors": { "version": "1.7.2", @@ -1511,17 +1511,17 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -1540,9 +1540,9 @@ "dev": true }, "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", "requires": { "minimatch": "^3.0.4" } @@ -1824,9 +1824,9 @@ } }, "jwks-rsa": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.1.tgz", - "integrity": "sha512-N7RsfrzK3+S+SqKEEhWF7Ak87Gzg0KcZq/f8h0VqL2ur3nTB6pi5J12uelGAzB3VfhWQI+zfolHE2XDu/EI7Hg==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.3.tgz", + "integrity": "sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA==", "requires": { "@types/express-jwt": "0.0.42", "axios": "^0.21.1", @@ -1913,9 +1913,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -1981,9 +1981,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2029,6 +2029,13 @@ "requires": { "pseudomap": "^1.0.1", "yallist": "^2.0.0" + }, + "dependencies": { + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } } }, "lru-memoizer": { @@ -2114,13 +2121,6 @@ "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } } }, "minizlib": { @@ -2140,9 +2140,9 @@ } }, "mocha": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", - "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -2246,21 +2246,6 @@ "isexe": "^2.0.0" } }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", @@ -2270,14 +2255,14 @@ } }, "mongodb": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.1.tgz", - "integrity": "sha512-uH76Zzr5wPptnjEKJRQnwTsomtFOU/kQEU8a9hKHr2M7y9qVk7Q4Pkv0EQVp88742z9+RwvsdTw6dRjDZCNu1g==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.6.tgz", + "integrity": "sha512-WlirMiuV1UPbej5JeCMqE93JRfZ/ZzqE7nJTwP85XzjAF4rRSeq2bGCb1cjfoHLOF06+HxADaPGqT0g3SbVT1w==", "requires": { - "bl": "^2.2.0", + "bl": "^2.2.1", "bson": "^1.1.4", "denque": "^1.4.1", - "require_optional": "^1.0.1", + "optional-require": "^1.0.2", "safe-buffer": "^5.1.2", "saslprep": "^1.0.0" } @@ -2306,9 +2291,9 @@ "dev": true }, "needle": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", - "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -2316,17 +2301,17 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -2379,9 +2364,9 @@ "dev": true }, "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", "requires": { "npm-normalize-package-bin": "^1.0.1" } @@ -2447,6 +2432,11 @@ "mimic-fn": "^2.1.0" } }, + "optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -2535,9 +2525,9 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "picomatch": { @@ -2658,9 +2648,9 @@ }, "dependencies": { "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", "dev": true } } @@ -2681,25 +2671,11 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -3044,13 +3020,6 @@ "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.3" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } } }, "text-table": { @@ -3285,33 +3254,33 @@ } }, "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.0.tgz", - "integrity": "sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", - "y18n": "^5.0.2", + "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.3.tgz", - "integrity": "sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww==" + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index 016a5e4..7c6d741 100644 --- a/package.json +++ b/package.json @@ -20,26 +20,26 @@ }, "homepage": "https://github.com/camicroscope/caboodle#readme", "dependencies": { - "@tensorflow/tfjs-node": "^2.7.0", + "@tensorflow/tfjs-node": "^2.8.6", "adm-zip": "^0.4.16", "atob": "^2.1.2", - "dotenv": "^8.2.0", + "dotenv": "^8.6.0", "express": "^4.17.1", - "helmet": "^4.1.0", + "helmet": "^4.6.0", "http-proxy-middleware": "^0.20.0", "inkjet": "^2.1.2", "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^1.12.1", - "mongodb": "^3.3.5", + "jwks-rsa": "^1.12.3", + "mongodb": "^3.6.6", "throng": "^4.0.0" }, "devDependencies": { - "chai": "^4.2.0", + "chai": "^4.3.4", "chai-http": "^4.3.0", "cookie-parser": "^1.4.4", "eslint": "^6.8.0", "eslint-config-google": "^0.14.0", "eslint-plugin-security": "^1.4.0", - "mocha": "^8.3.2" + "mocha": "^8.4.0" } } diff --git a/service/database/connector.js b/service/database/connector.js new file mode 100644 index 0000000..b24d580 --- /dev/null +++ b/service/database/connector.js @@ -0,0 +1,60 @@ +const { MongoClient } = require("mongodb"); + +/** + * @class MongoDBConnector + * @description This class is the single point configuration place for all + * operations that relate to the connection of the application with the + * database server. + * + * This class provides the connection objects that are used by database + * operations throughout the project. + */ +class MongoDBConnector { + /** + * @constructor to initialize connection to database server + */ + constructor() { + /** connection specifics */ + const connectionString = + process.env.MONGO_URI || "mongodb://127.0.0.1:27017"; + const databaseName = process.env.MONGO_DB || "camic"; + const url = `${connectionString}/${databaseName}`; + + /** connection configurations */ + const configs = { + useUnifiedTopology: true, + useNewUrlParser: true, + }; + + this.name = databaseName; + this.client = new MongoClient(url, configs); + } + + /** + * @async method to initialize the connection and store in application state + */ + async init() { + await this.client.connect(); + console.log(`[database:${this.name}] connected`); + this.db = {}; + this.db[this.name] = this.client.db(this.name); + } +} + +/** initialize an instance of mongoDB connection and kill process if connection fails */ +const connector = new MongoDBConnector(); + +/** + * to load connection instances in database operations + * @param {string} [databaseName=camic] Returns a connection to the said database + */ +const getConnection = (databaseName = "camic") => { + return connector.db[databaseName]; +}; + +/** export the connector to be used by utility functions */ +module.exports = { + getConnection, + connector, +}; + diff --git a/service/database/index.js b/service/database/index.js new file mode 100644 index 0000000..58cb7c8 --- /dev/null +++ b/service/database/index.js @@ -0,0 +1,177 @@ +const { getConnection } = require("./connector"); +const { transformIdToObjectId } = require("./util"); + +/** + * @class Mongo + * @description Handles database operations, called via handler. This is like a generic that + * is used through the project to perform basic operations on the database. + */ +class Mongo { + /** + * Runs the MongoDB find() method to fetch documents. + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName Name of the collection to run operation on + * @param {document} query Specifies selection filter using query operators. + * To return all documents in a collection, omit this parameter or pass an empty document ({}). + * @param {boolean} [transform=false] check to transform the IDs to ObjectID in response + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/ Read MongoDB Reference} + */ + static async find(database, collectionName, query, transform = true) { + try { + query = transformIdToObjectId(query); + + const collection = getConnection(database).collection(collectionName); + const data = await collection.find(query).toArray(); + + /** allow caller method to toggle response transformation */ + if (transform) { + data.forEach((x) => { + x["_id"] = { + $oid: x["_id"], + }; + }); + } + + return data; + } catch (e) { + console.error(e); + throw e; + } + } + + /** + * Runs a distinct find operation based on given query + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName Name of the collection to run operations on + * @param {string} upon Field for which to return distinct values. + * @param {Document} query A query that specifies the documents from + * which to retrieve the distinct values. + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.distinct Read MongoDB Reference} + */ + static async distinct(database, collectionName, upon, query) { + try { + const collection = getConnection(database).collection(collectionName); + const data = await collection.distinct(upon, query); + return data; + } catch (e) { + console.error(e); + throw e; + } + } + + /** + * Runs insertion operation to create an array of new documents + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName Name of collection to run operation on + * @param {Array} data Array of documents to insert into collection + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/ Read MongoDB Reference} + */ + static async add(database, collectionName, data) { + /** if not an array, transform into array */ + if (!Array.isArray(data)) { + data = [data]; + } + + try { + const collection = getConnection(database).collection(collectionName); + const res = await collection.insertMany(data); + return res; + } catch (e) { + console.error(e); + throw e; + } + } + + /** + * Runs the delete operation on the first document that satisfies the filter conditions + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName Name of collection to run operation on + * @param {document} query Specifies deletion criteria using query operators + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.deleteOne/ Read MongoDB Reference} + */ + static async delete(database, collectionName, filter) { + try { + filter = transformIdToObjectId(filter); + + const collection = getConnection(database).collection(collectionName); + const result = await collection.deleteOne(filter); + delete result.connection; + + return result; + } catch (e) { + console.error(e); + throw e; + } + } + + /** + * Runs aggregate operation on given pipeline + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName Name of collection to run operation on + * @param {Array} pipeline Array containing all the aggregation framework commands for the execution. + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/ Read MongoDB Reference} + */ + static async aggregate(database, collectionName, pipeline) { + try { + const collection = getConnection(database).collection(collectionName); + const result = await collection.aggregate(pipeline).toArray(); + return result; + } catch (e) { + console.error(e); + throw e; + } + } + + /** + * Runs updateOne operation on documents that satisfy the filter condition. + * + * @async + * @param {string} database Name of the database + * @param {string} collectionName name of collection to run operation on + * @param {document} filter selection criteria for the update + * @param {document|pipeline} updates modifications to apply to filtered documents, + * can be a document or a aggregation pipeline + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/ Read MongoDB Reference} + */ + static async update(database, collectionName, filter, updates) { + try { + filter = transformIdToObjectId(filter); + + const collection = await getConnection(database).collection( + collectionName + ); + const result = await collection.updateOne(filter, updates); + delete result.connection; + return result; + } catch (e) { + console.error(e); + throw e; + } + } +} + +/** export to be import using the destructuring syntax */ +module.exports = { + add: Mongo.add, + find: Mongo.find, + update: Mongo.update, + delete: Mongo.delete, + aggregate: Mongo.aggregate, + distinct: Mongo.distinct, +}; diff --git a/service/database/util.js b/service/database/util.js new file mode 100644 index 0000000..3dea949 --- /dev/null +++ b/service/database/util.js @@ -0,0 +1,25 @@ +const { ObjectID } = require("mongodb"); + +/** + * Utility function to transform id from String format to ObjectID format + * See attached link for full reference: + * + * @description if the query contains a _id property, transforms it to an + * ObjectID format. + * @param {Object} query Incoming Query for database operation + * + * @link https://docs.mongodb.com/manual/reference/method/ObjectId/ + */ +const transformIdToObjectId = (query) => { + const payload = { ...query }; + try { + if (payload["_id"]) { + payload["_id"] = new ObjectID(payload["_id"]); + } + return payload; + } catch (e) { + return query; + } +}; + +module.exports = { transformIdToObjectId }; diff --git a/setup_script.sh b/setup_script.sh new file mode 100755 index 0000000..c90afff --- /dev/null +++ b/setup_script.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +### +## ensure that required params are passed into script +### +if [ -n "$1" ]; then + HOST=$1 +else + echo "[ database ] : name not passed, set to default value (127.0.0.1)" + HOST="127.0.0.1" +fi + +### +## specify which database to operate on, if not specified, set default to camic +### +if [ -n "$2" ]; then + DB_NAME=$2 + echo "[ database ] : name set as ${DB_NAME}" +else + echo "[ database ] : name not passed, set to default value (camic)" + DB_NAME="camic" +fi + +### +## check if the system has required services installed +### +if ! command -v "mongo" &>/dev/null; then + echo "mongo could not be found on path. Please ensure that mongo is installed and is on PATH" + exit +fi + +if ! command -v "node" &>/dev/null; then + echo "node could not be found on path. Please ensure that node is installed and is on PATH" + exit +fi + +### +# check for the existence of required files in the Distro and camicroscope repository +### +echo "Checking for required files" + +#if [[ ! -d ../Distro/db ]] +#then echo "../Distro/db does not exist" +# exit +#fi + +if [[ ! -d ../Distro/config ]] +then echo "../Distro/config does not exist" + exit +fi + +if [[ ! -d ../Distro/jwt_keys ]] +then echo "../Distro/jwt_keys does not exist" + exit +fi + +if [[ ! -d ../caMicroscope ]] +then echo "../caMicroscope does not exist" + exit +fi + +echo "Required files exist." + +echo "Copying files..." + +#if [[ ! -d ../data ]] +#then +# mkdir ../data +# cp -r ../Distro/db ../data/ +#fi + +if [[ ! -d ../config ]] +then + mkdir ../config + cp -r ../Distro/config ../config/ +fi + +if [[ ! -d ./static ]] +then + mkdir ./static + cp ../Distro/config/login.html ./static/login.html +fi + +if [[ ! -d ./keys/jwt_keys ]] +then + cp -r ../Distro/jwt_keys ./keys/ +fi + +if [[ ! -f ./contentSecurityPolicy.json ]] +then + cp ../Distro/config/contentSecurityPolicy.json ./contentSecurityPolicy.json +fi + +if [[ ! -f ./static/additional_links.json ]] +then + cp ../Distro/config/additional_links.json ./static/additional_links.json +fi + +if [[ ! -d ./camicroscope ]] +then + cp -r ../caMicroscope ./camicroscope +fi + +echo "Copying files complete!" + + + + +### +## try connecting to mongodb instance +### +until mongo --host "${HOST}" --eval "print(\"Connected!\")" >/dev/null; do + sleep 2 +done +echo "[ database ] : connection established" + +### +## check if database exists +### +QUERY="db.getMongo().getDBNames().indexOf(\"${DB_NAME}\")" +COMMAND="mongo ${HOST} --eval '${QUERY}' --quiet" +if [ $(mongo ${HOST} --eval ${QUERY} --quiet) -lt 0 ]; then + echo "[ database ] : does not exist" + exit 1 +else + echo "[ database ] : database named ${DB_NAME} found" +fi + +### +## ask developer if they wish to seed the database +### +read -p "[ resource ] : Do you wish to initialize the database with indexes and configs? (y/n) : " yn +case $yn in +[Yy]*) + ### + ## Download the files from github and save to local directory + ### + echo "[ resource ] : downloading seeding files" + + # resource targets + RESOURCE_IDX="https://raw.githubusercontent.com/camicroscope/Distro/master/config/mongo_idx.js" + RESOURCE_COLLECTION="https://raw.githubusercontent.com/camicroscope/Distro/master/config/mongo_collections.js" + RESOURCE_DEFAULT_DATA="https://raw.githubusercontent.com/camicroscope/Distro/master/config/default_data.js" + + # get data from resource targets + wget -q RESOURCE_IDX -O .seeder.idx.js + wget -q RESOURCE_DEFAULT_DATA -O .seeder.default.js + wget -q RESOURCE_COLLECTION -O .seeder.collection.js + + echo "[ resource ] : clearing old configurations" + echo "[ resource ] : seeding collections" + mongo --quiet --host $HOST $DB_NAME .seeder.collection.js + echo "[ resource ] : seeding indexes" + mongo --quiet --host $HOST $DB_NAME .seeder.idx.js + echo "[ resource ] : seeding configurations" + mongo --quiet --host $HOST $DB_NAME .seeder.default.js + + ### + ## ask the user if they want to remove the seeding files + ### + read -p "[ resource ] : Do you wish to keep the seeding files generated ? (y/n) :" yn + case $yn in + [Yy]*) echo "[ resource ] : The seeder files are present in current directory with name : .seeder.*.js" ;; + [Nn]*) rm .seeder.* ;; + *) echo "[ resource ] : Please answer y/n." ;; + esac + ;; + ### seeder file cleanup ends here + +[Nn]*) echo "[ resource ] : database initialization skipped" ;; +*) echo "[ resource ] : skipped" ;; +esac +### seeder prompt ends here + +### +## load the default routes.json file if it does not exist in the file system +### +if [ -f "routes.json" ]; then + echo "[ routes ] : routes.json file already exists" +else + cp routes.json.example routes.json + echo "[ routes ] : routes.json file generated from routes.json.example" +fi + +if [ -f "keys/key" ] && [ -f "keys/key.pub" ]; then + echo "[ keys ] : public and private keys already exist" +else + bash ./keys/make_key.sh + echo "[ routes ] : routes.json file generated from routes.json.example" +fi + +### +## install the packages if not done already +### +if [ ! -d "node_modules" ]; then + echo "[ modules ] : packages not installed, running : npm install" + npm install +else + echo "[ modules ] : packages directory already exist, to reinstall, delete the node_modules folder and run npm install" +fi + +### +## initialize the environment variable configs +### +if [ ! -f ".env" ]; then + echo "[ env ] : .env file not found" + cp .env.example .env +else + echo "[ env ] : .env file already exists" +fi + +echo "" +echo "If you face issues due to permissions and login handlers, setting the DISABLE_SEC to false in .env file might help." diff --git a/static/additional_links.json b/static/additional_links.json new file mode 100644 index 0000000..00f1ad5 --- /dev/null +++ b/static/additional_links.json @@ -0,0 +1,8 @@ +[ + { + "displayName":"Bug Report", + "url":"https://goo.gl/forms/mgyhx4ADH0UuEQJ53", + "icon": "bug_report", + "openInNewTab": true + } +] diff --git a/static/login.html b/static/login.html index 05602aa..ff760a1 100644 --- a/static/login.html +++ b/static/login.html @@ -1,77 +1,159 @@ + - - -
-Sign out - - + + + +
+
+
+

caMicroscope

+
+ -function getUrlParam(name, url) { - if (!url) url = window.location.href; - name = name.replace(/[\[\]]/g, '\\$&'); - var regex = new RegExp('[?&#]' + name + '(=([^&#]*)|&|#|$)'), - results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, ' ')); -} +
+

caMicroscope is a tool to view, label, and annotate biomedical images.

+ -function onSignIn(googleUser) { - var id_token = googleUser.getAuthResponse().id_token; - console.info(id_token) - // trade for camic token - var cookie_name = "token" // "token" is expected by elevate router - var base_deployment_url = window.location.toString().split("/").slice(0,-1).join("/") - var redirect_uri = base_deployment_url + "/login.html" - var default_redirect = base_deployment_url + "/apps/table.html" - var state - if (getUrlParam("state")) - { - state = decodeURIComponent(getUrlParam("state")) - } - if (!state){ - state = default_redirect - } +
- if (id_token){ - document.cookie = cookie_name + "=" + id_token; - fetch("./auth/Token/check", - {headers: { - 'Authorization': "Bearer " + id_token - }} - ).then(x=>x.json()).then(x=>{ - console.log("{id provider", id_token) - console.log("{auth service}", x) - if (x.hasOwnProperty('token')){ - document.cookie = cookie_name + "=" + x.token; - window.location = state +
+ +
+ +
+

Please Sign In With Your Google Account

+
+
+
+
+
+ + - + function signOut() { + var auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(function() { + console.log("User signed out."); + }); + } + + function deleteCookies() { + var allcookies = document.cookie.split(";"); + for (var i = 0; i < allcookies.length; i++) { + var cookie = allcookies[i]; + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + } + } + + // google's jwk is here: https://www.googleapis.com/oauth2/v3/certs + function randomString(length) { + var bytes = new Uint8Array(length); + var random = window.crypto.getRandomValues(bytes); + var result = []; + var charset = + "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._~"; + random.forEach(function(c) { + result.push(charset[c % charset.length]); + }); + return result.join(""); + } + + function getUrlParam(name, url) { + if (!url) url = window.location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ""; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + + + + + + + diff --git a/test/unit/database.js b/test/unit/database.js new file mode 100644 index 0000000..88157ea --- /dev/null +++ b/test/unit/database.js @@ -0,0 +1,386 @@ +const chai = require("chai"); +const {ObjectID} = require("mongodb"); +var should = chai.should(); + +const { + getConnection, + connector, +} = require("./../../service/database/connector"); +const MongoDB = require("./../../service/database/index"); +const Util = require("./../../service/database/util"); + +/** + * The services are designed to operate as independent units and therefore by + * design, must not depend / interfere with the main codebase. This allows + * development and debugging without touching the application codebase. + */ +const DB = { + NAME: "camic", + COLLECTION: "users", +}; + +/** + * all tests for service/database + */ +describe("service/database", () => { + /** wait for connection before initiating tests */ + before(async () => { + const connection = () => + new Promise((resolve) => { + connector + .init() + .then(() => { + resolve(true); + }) + .catch(() => { + console.error("Error connecting to database"); + }); + }); + await connection(); + }); + + /** test suite for connector */ + describe("connector", () => { + it("should be defined and callable", () => { + getConnection.should.not.be.undefined; + (typeof getConnection).should.equal("function"); + }); + + it("should return a valid connection when no argument provided", () => { + const connection = getConnection(); + connection.should.not.be.undefined; + }); + + it("should return a valid connection when database name provided", () => { + const connection = getConnection("camic"); + connection.should.not.be.undefined; + connection.serverConfig.should.not.be.undefined; + }); + + it("should inject configuration objects", () => { + const connection = getConnection("camic"); + connection.serverConfig.s.options.useUnifiedTopology.should.be.true; + }); + }); + + /** test suite for utility functions */ + describe("util", () => { + it("should be defined and callable", () => { + Util.should.not.be.undefined; + Util.transformIdToObjectId.should.not.be.undefined; + (typeof Util.transformIdToObjectId).should.equal("function"); + }); + + it("should not alter argument if property _id not exist", () => { + const original = { + something: "awesome", + repo: "caracal", + valid: true, + version: 1, + string: "F", + }; + + const processed = Util.transformIdToObjectId(original); + processed.should.eql(processed); + }); + + it("should not alter original object passed into function", () => { + const original = { + foo: "bar", + number: 1, + _id: "507f1f77bcf86cd799439011", + }; + + const processed = Util.transformIdToObjectId(original); + (typeof original._id).should.be.equal("string"); + (typeof processed._id).should.be.equal("object"); + }); + + it("should alter datatype of property with valid _id", () => { + const original = { + foo: "bar", + number: 1, + _id: "507f1f77bcf86cd799439011", + }; + + (typeof original._id).should.be.equal("string"); + const processed = Util.transformIdToObjectId(original); + (typeof processed._id).should.be.equal("object"); + }); + + it("should not break if datatype of id not a valid ObjectID", () => { + const original = { + foo: "bar", + number: 1, + _id: "507f1_invalid_6cd799439011", + }; + + (typeof original._id).should.be.equal("string"); + const processed = Util.transformIdToObjectId(original); + (typeof processed._id).should.be.equal("string"); + }); + }); + + /** test suite for core functionality */ + describe("service", () => { + /** dummy payload for unit tests */ + const USERS = [ + { + _id: new ObjectID(), + name: "user 1", + age: 20, + config: { + foo: "bar", + }, + }, + { + _id: new ObjectID(), + name: "user 2", + age: 20, + config: { + foo: "bar", + }, + }, + ]; + + /** seed values before execution of each unit */ + beforeEach(async () => { + await getConnection().collection(DB.COLLECTION).insertMany(USERS); + }); + + /** clear database after each unit */ + afterEach(async () => { + await getConnection().collection(DB.COLLECTION).deleteMany({age: 20}); + await getConnection() + .collection(DB.COLLECTION) + .deleteMany({for: "aggregate"}); + }); + + /** ensures that service always provides all database functionality */ + it("should be defined and callable", () => { + MongoDB.should.not.be.undefined; + (typeof MongoDB).should.be.equal("object"); + (typeof MongoDB.add).should.be.equal("function"); + (typeof MongoDB.aggregate).should.be.equal("function"); + (typeof MongoDB.delete).should.be.equal("function"); + (typeof MongoDB.distinct).should.be.equal("function"); + (typeof MongoDB.find).should.be.equal("function"); + (typeof MongoDB.update).should.be.equal("function"); + }); + + describe(".add", () => { + /** if it's callable, it means it's defined. */ + it("should be defined and callable", () => { + (typeof MongoDB.add).should.be.equal("function"); + }); + + /** normal insert operations for single document */ + it("should insert single document into collection", async () => { + const user = { + _id: new ObjectID(), + name: "testUser1", + age: 20, + config: { + foo: "bar", + }, + }; + + const res = await MongoDB.add(DB.NAME, DB.COLLECTION, user); + res.insertedCount.should.be.equal(1); + res.result.n.should.be.equal(1); + }); + + /** should */ + it("should insert multiple document into collection", async () => { + const users = [ + { + _id: new ObjectID(), + name: "testUser1", + age: 20, + config: { + foo: "bar", + }, + }, + { + _id: new ObjectID(), + name: "testUser2", + age: 20, + config: { + foo: "bar", + }, + }, + ]; + + const res = await MongoDB.add(DB.NAME, DB.COLLECTION, users); + res.insertedCount.should.be.equal(2); + res.result.n.should.be.equal(2); + }); + }); + + /** + * @todo: unit tests for following methods + * .aggregate() + */ + describe(".delete", () => { + /** if its callable, means its defined */ + it("should be defined and callable", () => { + (typeof MongoDB.delete).should.be.equal("function"); + }); + + it("should delete a single document by id", async () => { + const res = await MongoDB.delete(DB.NAME, DB.COLLECTION, { + _id: USERS[0]._id, + }); + res.result.ok.should.be.equal(1); + res.deletedCount.should.be.equal(1); + }); + + it("should only delete one document even if filter matches many", async () => { + const res = await MongoDB.delete(DB.NAME, DB.COLLECTION, { + age: 20, + }); + res.result.ok.should.be.equal(1); + res.deletedCount.should.be.equal(1); + }); + + it("should not throw error if filter returns empty data", async () => { + const res = await MongoDB.delete(DB.NAME, DB.COLLECTION, { + age: 50, + }); + res.result.ok.should.be.equal(1); + res.deletedCount.should.be.equal(0); + }); + }); + + describe(".find", () => { + /** if its callable, means its defined */ + it("should be defined and callable", () => { + (typeof MongoDB.find).should.be.equal("function"); + }); + + it("should list all data when empty filter provided", async () => { + const result = await MongoDB.find(DB.NAME, DB.COLLECTION, {}); + result.length.should.be.equal(2); + }); + + it("should list all data matching given filter", async () => { + const result = await MongoDB.find(DB.NAME, DB.COLLECTION, {age: 20}); + result.length.should.be.equal(2); + }); + + it("should list specific document when filtered via unique id", async () => { + const result = await MongoDB.find(DB.NAME, DB.COLLECTION, { + _id: USERS[0]._id, + }); + result.length.should.be.equal(1); + result[0].name.should.equal(USERS[0].name); + }); + + it("should return empty array if no data matches given filter", async () => { + const result = await MongoDB.find(DB.NAME, DB.COLLECTION, {age: 21}); + result.length.should.be.equal(0); + }); + }); + + describe(".update", () => { + it("should be defined and callable", () => { + (typeof MongoDB.update).should.be.equal("function"); + }); + + it("should update a single document even when filter matches multiple items", async () => { + const res = await MongoDB.update( + DB.NAME, + DB.COLLECTION, + {age: 20}, + { + $set: {name: "new name"}, + }, + ); + res.modifiedCount.should.equal(1); + res.matchedCount.should.equal(1); + }); + + it("should not update any document when filters do not match any document", async () => { + const res = await MongoDB.update( + DB.NAME, + DB.COLLECTION, + {age: 50}, + { + $set: {name: "new name"}, + }, + ); + res.modifiedCount.should.equal(0); + res.matchedCount.should.equal(0); + }); + }); + + describe(".distinct", () => { + it("should be defined and callable", () => { + (typeof MongoDB.distinct).should.be.equal("function"); + }); + + it("should return array of distinct values of passed filter", async () => { + const res = await MongoDB.distinct(DB.NAME, DB.COLLECTION, "age", {}); + res.length.should.be.equal(1); + res[0].should.be.equal(20); + }); + + it("should return all elements if none repeated in passed filter", async () => { + const res = await MongoDB.distinct(DB.NAME, DB.COLLECTION, "name", {}); + res.length.should.be.equal(2); + }); + }); + + describe(".aggregate", () => { + it("should be defined and callable", () => { + (typeof MongoDB.aggregate).should.be.equal("function"); + }); + + it("should run a function pipeline on data", async () => { + await MongoDB.add(DB.NAME, DB.COLLECTION, [ + { + _id: new ObjectID(), + name: "user 1", + type: "a", + by: "bot", + price: 10, + for: "aggregate", + }, + { + _id: new ObjectID(), + name: "user 2", + type: "a", + by: "bot", + price: 20, + for: "aggregate", + }, + { + _id: new ObjectID(), + name: "user 3", + type: "b", + by: "human", + price: 30, + for: "aggregate", + }, + { + _id: new ObjectID(), + name: "user 4", + type: "b", + by: "human", + price: 40, + for: "aggregate", + }, + ]); + + const res = await MongoDB.aggregate(DB.NAME, DB.COLLECTION, [ + {$match: {type: "a"}}, + {$group: {_id: "$by", total: {$sum: "$price"}}}, + {$sort: {total: -1}}, + ]); + + res.length.should.be.equal(1); + res[0]._id.should.be.equal("bot"); + }); + }); + }); +});