diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..c91bcf2 --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,46 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Container Publish + +on: + push: + branches: ['master', 'develop'] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index f49610a..f5366fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN npm install ARG viewer ARG fork RUN git clone https://github.com/${fork:-camicroscope}/camicroscope.git --branch=${viewer:-master} -EXPOSE 8010 +EXPOSE 4010 RUN chgrp -R 0 /src && \ chmod -R g+rwX /src diff --git a/README.md b/README.md index cf84b4e..0b88a2c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ All possible configuration variables are listed in `.env.example`. Renaming the |IIP_PATH | IIP server location | http://ca-iip | |MONGO_URI | mongo connection uri | mongodb://localhost | |MONGO_DB | mongo db to use, default camic | +|RUN_INDEXER | add indexes and defaults for mongo, default true | |GENERATE_KEY_IF_MISSING | automatic generate key in server in not found | false | |ENABLE_SECURITY_AT| time at which to enable security; [see parsable times](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse)| (not active) | diff --git a/caracal.js b/caracal.js index 5add0dd..4ab7d4a 100644 --- a/caracal.js +++ b/caracal.js @@ -29,6 +29,7 @@ if (!DISABLE_TF) { const Model = require('./handlers/modelTrainer.js'); } + const {connector} = require("./service/database/connector"); var WORKERS = process.env.NUM_THREADS || 4; @@ -39,6 +40,8 @@ var MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost'; var DISABLE_CSP = process.env.DISABLE_CSP || false; +var RUN_INDEXER = process.env.RUN_INDEXER || true; + const app = express(); app.use(cookieParser()); @@ -194,38 +197,58 @@ app.use(function(err, req, res, next) { res.status(statusCode).json(err); }); -var startApp = function(app) { - return function() { - // Prepare for SSL/HTTPS - var httpsOptions = {}; - try { - var sslPkPath = "./ssl/privatekey.pem"; - var sslCertPath = "./ssl/certificate.pem"; - if (fs.existsSync(sslPkPath) && fs.existsSync(sslCertPath)) { - console.info("Starting in HTTPS Mode mode"); - httpsOptions.key = fs.readFileSync(sslPkPath, 'utf8'); - httpsOptions.cert = fs.readFileSync(sslCertPath, 'utf8'); - } - } catch (err) { - console.error(err); - } - if (httpsOptions.key && httpsOptions.cert) { - https.createServer(httpsOptions, app).listen(PORT, () => console.log('listening HTTPS on ' + PORT)); - } else { - app.listen(PORT, () => console.log('listening on ' + PORT)); +function startApp(app) { + // Prepare for SSL/HTTPS + var httpsOptions = {}; + try { + var sslPkPath = "./ssl/privatekey.pem"; + var sslCertPath = "./ssl/certificate.pem"; + if (fs.existsSync(sslPkPath) && fs.existsSync(sslCertPath)) { + console.info("Starting in HTTPS Mode mode"); + httpsOptions.key = fs.readFileSync(sslPkPath, 'utf8'); + httpsOptions.cert = fs.readFileSync(sslCertPath, 'utf8'); } - }; + } catch (err) { + console.error(err); + } + if (httpsOptions.key && httpsOptions.cert) { + https.createServer(httpsOptions, app).listen(PORT, () => console.log('listening HTTPS on ' + PORT)); + } else { + app.listen(PORT, () => console.log('listening on ' + PORT)); + } }; -throng(WORKERS, startApp(app)); +// call this only once no matter what +function masterHandler() { + connector.init().then(() => { + const handler = new DataTransformationHandler(MONGO_URI, './json/configuration.json'); + handler.startHandler(); + }).then(()=>{ + if (RUN_INDEXER) { + const indexer = require('./idx_mongo.js'); + indexer.collections(); + indexer.indexes(); + indexer.defaults(); + console.log("added indexes"); + } + }).catch((e) => { + console.error(e); + process.exit(1); + }); +} +// for each worker +function workerHandler() { + connector.init().then(() => { + const handler = new DataTransformationHandler(MONGO_URI, './json/configuration.json'); + handler.startHandler(); + }).then(()=>{ + startApp(app); + }).catch((e) => { + console.error(e); + process.exit(1); + }); +} -/** 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); -}); +throng({master: masterHandler, start: workerHandler, count: WORKERS}); module.exports = app; // for tests diff --git a/fixAnnotationLabelIDPatch.js b/fixAnnotationLabelIDPatch.js new file mode 100644 index 0000000..600ecbe --- /dev/null +++ b/fixAnnotationLabelIDPatch.js @@ -0,0 +1,143 @@ +const {MongoClient, ObjectID} = require("mongodb"); + +function randomId() { + // Math.random should be unique because of its seeding algorithm. + // Convert it to base 36 (numbers + letters), and grab the first 9 characters + // after the decimal. + return `_${Math.random() + .toString(36) + .substr(2, 9)}`; +} +// +class FixAnnotationLabelIDPatch { + /** + * @constructor to initialize connection to database server + */ + constructor(mongoUrl="mongodb://ca-mongo", db="camic", recordPerBatch=20000) { + /** connection specifics */ + this.mongoUrl = mongoUrl; + this.db = db; + this.labelMap = new Map(); + /** connection configurations */ + const configs = { + useUnifiedTopology: true, + useNewUrlParser: true, + }; + this.client = new MongoClient(this.mongoUrl, configs); + + // deal batch + this.totalBatch; + this.totalRecord; + this.currentBatch = 1; + this.recordPerBatch = recordPerBatch; + } + async run() { + console.log('FixAnnotationLabelIDPatch Start'); + + console.log(`waiting to connect ${this.mongoUrl}/${this.db}`); + await this.client.connect(); + console.log(`[database:${this.db}] connected`); + // get connector + const connector = this.client.db(this.db); + + // config_prelabel + const configCollection = connector.collection('configuration'); + const query = {'config_name': 'preset_label'}; + const opt = {}; + const configData = await configCollection.findOne(query, opt); + + // clear the label map + this.labelMap.clear(); + configData.configuration.forEach((label)=>{ + this.labelMap.set(label.type, label); + }); + + // preset label annotation without labelId + // provenance.analysis.labelId + // properties.annotations.labelId + const markCollection = connector.collection('mark'); + this.totalRecord = await markCollection.countDocuments({ + 'provenance.analysis.source': 'human', + 'provenance.analysis.type': 'label', + 'provenance.analysis.labelId': {$exists: false}, + }); + + // get total batch + this.totalBatch = Math.ceil(this.totalRecord/this.recordPerBatch); + + + console.log(`TOTAL Annotations Batches: ${this.totalBatch}`); + + while (this.currentBatch <= this.totalBatch) { + const isCompleted = await this.runCurrentBatch(markCollection); + if (isCompleted) console.log(`Batch ${this.currentBatch} Completed`); + this.currentBatch++; + } + + console.log('FixAnnotationLabelIDPatch End'); + return true; + } + + async runCurrentBatch(collection) { + console.log(`Running ${this.currentBatch} of ${this.totalBatch} Batches`); + + // get current batch marks + const marks = await collection.find({ + 'provenance.analysis.source': 'human', + 'provenance.analysis.type': 'label', + 'provenance.analysis.labelId': {$exists: false}, + }, { + geometries: 0, + skip: 0, + limit: this.recordPerBatch, + }).toArray(); + + for (let idx = 0; idx < marks.length; idx++) { + const mark = marks[idx]; + + console.log(`(${idx+1}/${marks.length}) => mark Id: ${mark._id}`); + const execId = mark.provenance.analysis.execution_id; + const executionId = execId.slice(execId.lastIndexOf('_')); + const labelType = mark.properties.annotations.notes; + // check if the label type on current marks exist on label map + var labelId; + if (this.labelMap.has(labelType)) { + const val = this.labelMap.get(labelType); + labelId = val.id; + } else { + // create a random Id for missing label type + labelId = randomId(); + this.labelMap.set(labelType, {id: labelId}); + } + // update annotation + const rs = await collection.updateOne({_id: new ObjectID(mark._id)}, { + '$set': { + 'provenance.analysis.execution_id': executionId, + 'provenance.analysis.labelId': labelId, + 'provenance.analysis.name': labelType, + 'properties.annotations.id': executionId, + 'properties.annotations.labelId': labelId, + 'properties.annotations.name': labelType, + }, + }); + if (rs.result.ok&&rs.result.nModified) { + console.log('update success'); + } else { + console.error('update fail'); + } + } + return true; + } +} +async function start() { + try { + const patch = new FixAnnotationLabelIDPatch(); + const isCompleted = await patch.run(); + if (isCompleted) process.exit(); + } catch (error) { + console.error(error); + process.exit(1); + } +} + +start(); diff --git a/idx_mongo.js b/idx_mongo.js new file mode 100644 index 0000000..242b485 --- /dev/null +++ b/idx_mongo.js @@ -0,0 +1,481 @@ +// Create indexes and essential defaults for mongo +// replace this file before startup (e.g. container mount) to change how this runs + +const mongodb = require("./service/database"); + +function indexes() { + db = "camic"; + mongodb.createIndex(db, "authorization", {"name": 1}, {unique: true}); + mongodb.createIndex(db, "user", {"email": 1}, {unique: true}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1, + "provenance.analysis.execution_id": 1, "footprint": 1, "x": 1, "y": 1}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1, + "provenance.analysis.execution_id": 1, "provenance.analysis": 1}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1, "provenance.analysis": 1}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1, "provenance.analysis.execution_id": 1}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1, "provenance.analysis.source": 1}); + mongodb.createIndex(db, "mark", {"provenance.image.slide": 1}); + mongodb.createIndex(db, "mark", {"provenance.analysis.labelId": 1}); + mongodb.createIndex(db, "slide", {'study': 1, 'specimen': 1, 'name': 1}); + mongodb.createIndex(db, "template", {'id': 1}); + mongodb.createIndex(db, "template", {'name': 1}); + mongodb.createIndex(db, "heatmap", {"provenance.image.slide": 1, "provenance.analysis.execution_id": 1}); + mongodb.createIndex(db, "heatmap", {"provenance.image.slide": 1}); + mongodb.createIndex(db, "heatmapEdit", {"provenance.image.slide": 1, + "provenance.analysis.execution_id": 1, "user_id": 1}); + mongodb.createIndex(db, "configuration", {'config_name': 1}, {unique: true}); +} + +function collections() { + db = "camic"; + mongodb.createCollection(db, "slide", { + validator: { + $jsonSchema: { + bsonType: 'object', + required: ['name', 'location'], + properties: { + name: { + bsonType: 'string', + description: 'Slide display name', + }, + location: { + bsonType: 'string', + description: 'Slide location, used for opening', + }, + }, + }, + }, + }, true); + + mongodb.createCollection(db, "mark", { + validator: { + $jsonSchema: { + bsonType: 'object', + required: ['provenance'], + properties: { + provenance: { + bsonType: 'object', + required: ['image', 'analysis'], + properties: { + image: { + bsonType: 'object', + required: ['slide'], + }, + analysis: { + bsonType: 'object', + required: ['execution_id'], + }, + }, + }, + }, + }, + }, + }, true); + + mongodb.createCollection(db, "heatmap", { + validator: { + $jsonSchema: { + bsonType: 'object', + required: ['provenance'], + properties: { + provenance: { + bsonType: 'object', + required: ['image', 'analysis'], + properties: { + image: { + bsonType: 'object', + required: ['slide'], + }, + analysis: { + bsonType: 'object', + required: ['execution_id'], + }, + }, + }, + }, + }, + }, + }, true); + + mongodb.createCollection(db, "heatmapedit", { + validator: { + $jsonSchema: { + bsonType: 'object', + required: ['user_id', 'provenance'], + properties: { + provenance: { + bsonType: 'object', + required: ['image', 'analysis'], + properties: { + image: { + bsonType: 'object', + required: ['slide'], + }, + analysis: { + bsonType: 'object', + required: ['fields', 'execution_id'], + }, + }, + }, + }, + }, + }, + }, true); + + mongodb.createCollection(db, "template", { + validator: { + $jsonSchema: { + bsonType: 'object', + required: ['id', 'name', 'properties'], + properties: { + id: { + bsonType: 'string', + description: 'template identifier', + }, + name: { + bsonType: 'string', + description: 'template display name', + }, + properties: { + bsonType: 'object', + description: 'pure-form questions object', + additionalProperties: { + bsonType: 'object', + required: ['title', 'type'], + }, + }, + }, + }, + }, + }, true); +} + +function defaults() { + db = "camic"; + let defaultTemplate = { + "_id": "0", + "type": "object", + "id": "annotation-form", + "name": "AnnotSchema", + "description": "", + "links": [], + "additionalProperties": false, + "properties": { + "name": { + "id": "a0", + "title": "Identity Name", + "type": "string", + "required": true, + "description": "note name", + }, "notes": { + "id": "a1", + "title": "Notes: ", + "type": "string", + "format": "textarea", + "maxLength": 128, + }, + }, + }; + + mongodb.add(db, 'template', defaultTemplate, true); + + var defaultConfigs = [{ + "configuration": [ + { + "id": "001", + "color": "#ff6296", + "mode": "grid", + "type": "Lymph-Positive", + "size": 100, + }, + { + "id": "002", + "color": "#ff6296", + "mode": "point", + "type": "Lymph-Positive", + }, + { + "id": "003", + "color": "#62ffcb", + "mode": "grid", + "type": "Lymph-Negative", + "size": 100, + }, + { + "id": "004", + "color": "#62ffcb", + "mode": "point", + "type": "Lymph-Negative", + }, + { + "id": "005", + "color": "#ffcb62", + "mode": "grid", + "type": "Neutrophil-Positive", + "size": 50, + }, + { + "color": "#6296ff", + "mode": "grid", + "type": "Neutrophil-Negative", + "size": 50, + "id": "006", + }, + { + "color": "#ff00d9", + "mode": "grid", + "type": "Necrosis-Positive", + "size": 100, + "id": "007", + }, + { + "color": "#ff00d9", + "mode": "grid", + "type": "Necrosis-Positive", + "size": 500, + "id": "008", + }, + { + "color": "#00ff26", + "mode": "grid", + "type": "Necrosis-Negative", + "size": 100, + "id": "009", + }, + { + "color": "#00ff26", + "mode": "grid", + "type": "Necrosis-Negative", + "size": 500, + "id": "010", + + }, + { + "color": "#790cff", + "mode": "grid", + "type": "Tumor-Positive", + "size": 100, + "id": "011", + }, + { + "color": "#790cff", + "mode": "grid", + "type": "Tumor-Positive", + "size": 300, + "id": "012", + }, + { + "color": "#790cff", + "mode": "grid", + "type": "Tumor-Positive", + "size": 1000, + "id": "013", + }, + { + "color": "#790cff", + "mode": "grid", + "type": "Tumor-Positive", + "size": 2000, + "id": "014", + }, + { + "color": "#92ff0c", + "mode": "grid", + "type": "Tumor-Negative", + "size": 100, + "id": "015", + }, + { + "color": "#92ff0c", + "mode": "grid", + "type": "Tumor-Negative", + "size": 300, + "id": "016", + }, + { + "color": "#92ff0c", + "mode": "grid", + "type": "Tumor-Negative", + "size": 1000, + "id": "017", + }, + { + "color": "#92ff0c", + "mode": "grid", + "type": "Tumor-Negative", + "size": 2000, + "id": "018", + }, { + "color": "#8dd3c7", + "mode": "free", + "type": "Prostate-Benign", + "id": "019", + }, { + "color": "#ffffb3", + "mode": "free", + "type": "Prostate-Gleason3", + "id": "020", + }, { + "color": "#bebada", + "mode": "free", + "type": "Prostate-Gleason4", + "id": "021", + }, { + "color": "#fb8072", + "mode": "free", + "type": "Prostate-Gleason5", + "id": "022", + }, { + "color": "#80b1d3", + "mode": "free", + "type": "Prostate-CancerNOS", + "id": "023", + }, { + "color": "#fdb462", + "mode": "free", + "type": "NSCLC-Benign", + "id": "024", + }, { + "color": "#b3de69", + "mode": "free", + "type": "NSCLC-SquamousCA", + "id": "025", + }, { + "color": "#fccde5", + "mode": "free", + "type": "NSCLC-AdenoCA(all)", + "id": "026", + }, { + "color": "#d9d9d9", + "mode": "free", + "type": "NSCLC-Acinar", + "id": "027", + }, { + "color": "#bc80bd", + "mode": "free", + "type": "NSCLC-Lapidic", + "id": "028", + }, { + "color": "#ccebc5", + "mode": "free", + "type": "NSCLC-Solid", + "id": "029", + }, { + "color": "#ffed6f", + "mode": "free", + "type": "NSCLC-Papillary", + "id": "030", + }, { + "color": "#6a3d9a", + "mode": "free", + "type": "NSCLC-Micropapillary", + "id": "031", + }, + ], + "config_name": "preset_label", + "version": "1.0.0", + }]; + + + mongodb.add(db, 'configuration', defaultConfigs, true); + + var defaultLinks = { + "configuration": [ + { + "displayName": "Bug Report", + "url": "https://goo.gl/forms/mgyhx4ADH0UuEQJ53", + "icon": "bug_report", + "openInNewTab": true, + }, + ], + "config_name": "additional_links", + "version": "1.0.0", + }; + mongodb.add(db, 'configuration', defaultLinks, true); + + var evaluationForm = { + "config_name": "evaluation_form", + "enable": true, + "configuration": { + "schema": { + "type": "object", + "properties": { + "slide_quality": { + "type": "string", + "title": "Slide Quality", + "enum": ["satisfactory", "unsatisfactory"], + "default": "satisfactory", + "required": true, + }, + "tumor_presence": { + "type": "string", + "title": "Tumor Presence", + "enum": ["presence", "absent"], + "default": "presence", + "required": true, + }, + "informativeness": { + "type": "string", + "title": "Informativeness", + "enum": ["informative", "uninformative"], + "default": "informative", + "required": true, + }, + "score": { + "minimum": 0, + "maximum": 100, + "default": 50, + }, + "wrong_tumor_type": { + "type": "boolean", + "default": false, + }, + "tumor_note": { + "type": "string", + "required": true, + }, + + }, + "dependencies": { + "tumor_note": ["wrong_tumor_type"], + "score": ["informativeness"], + }, + }, + "options": { + "fields": { + "score": { + "type": "integer", + "label": "Score", + "slider": true, + "dependencies": { + "informativeness": "informative", + }, + }, + "wrong_tumor_type": { + "rightLabel": "Wrong Tumor Type", + "helper": "Check If Tumor Type Is Wrong", + "helpersPosition": "above", + }, + "tumor_note": { + "type": "textarea", + "label": "Tumor Note", + "helper": "Please Enter the Correct Tumor Type", + "helpersPosition": "above", + "rows": 2, + "dependencies": { + "wrong_tumor_type": true, + }, + }, + }, + }, + }, + "version": "1.0.0", + }; + mongodb.add(db, 'configuration', evaluationForm, true); +} + +module.exports = { + indexes: indexes, + collections: collections, + defaults: defaults, +}; diff --git a/package-lock.json b/package-lock.json index d37df0f..918501b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -284,11 +284,18 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "requires": { - "follow-redirects": "^1.10.0" + "follow-redirects": "^1.14.0" + }, + "dependencies": { + "follow-redirects": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + } } }, "balanced-match": { @@ -1025,27 +1032,9 @@ "dev": true }, "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "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": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "form-data": { "version": "2.3.3", @@ -1611,11 +1600,6 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2570,11 +2554,11 @@ "dev": true }, "throng": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/throng/-/throng-4.0.0.tgz", - "integrity": "sha1-mDxroZk7WOroWZmKpof/6I34TBc=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throng/-/throng-5.0.0.tgz", + "integrity": "sha512-nrq7+qQhn/DL8yW/wiwImTepfi6ynOCAe7moSwgoYN1F32yQMdBkuFII40oAkb3cDfaL6q5BIoFTDCHdMWQ8Pw==", "requires": { - "lodash.defaults": "^4.0.1" + "lodash": "^4.17.20" } }, "through": { diff --git a/package.json b/package.json index 43e815d..71c5649 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "jsonwebtoken": "^8.5.1", "jwks-rsa": "^1.12.3", "mongodb": "^3.6.6", - "throng": "^4.0.0" + "throng": "^5.0.0" }, "devDependencies": { "chai": "^4.3.4", diff --git a/routes.json b/routes.json new file mode 100644 index 0000000..c3b6cba --- /dev/null +++ b/routes.json @@ -0,0 +1,495 @@ +[ + { + "method":"static", + "use":"static" + },{ + "method":"static", + "use":"camicroscope" + }, + + { + "method":"use", + "route": "/loader/", + "handlers":[ + {"function": "loginHandler", "args":[]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]} + ] + },{ + "method":"use", + "route": "/loader/slide/delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin"]]} + ] + },{ + "method":"use", + "route": "/loader/", + "handlers":[ + {"function":"proxyHandler", "args": ["http://ca-load:4000/"]} + ] + },{ + "method":"use", + "route": "/googleAuth/", + "handlers":[ + {"function":"proxyHandler", "args": ["http://ca-load:4001/"]} + ] + }, + + { + "method":"use", + "route": "/img/IIP/raw/", + "handlers":[ + {"function": "loginHandler", "args":[]}, + {"function":"iipHandler", "args": []} + ] + }, + + { + "route":"/data", + "method":"use", + "handlers":[ + {"function":"loginHandler", "args": []}, + {"function":"sanitizeBody", "args": []} + ] + }, + + { + "route":"/data/Slide/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "slide"]}, + {"function":"filterHandler", "args": ["data", "userFilter", "filter"]} + ] + },{ + "route":"/data/Slide/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "slide"]} + ] + },{ + "route":"/data/Slide/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoFind", "args": ["camic", "slide"]}, + {"function":"editHandler", "args": ["data", "userFilter", "filter"]}, + {"function":"mongoDelete", "args": ["camic", "slide"]} + ] + },{ + "route":"/data/Slide/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoFind", "args": ["camic", "slide"]}, + {"function":"editHandler", "args": ["data", "userFilter", "filter"]}, + {"function":"mongoUpdate", "args": ["camic", "slide"]} + ] + }, + + { + "route":"/data/Request/find", + "method":"get", + "handlers":[ + {"function":"permissionHandler", "args": [["Editor", "Admin"]]}, + {"function":"mongoFind", "args": ["camic", "request"]} + ] + },{ + "route":"/data/Request/post", + "method":"post", + "handlers":[ + {"function":"mongoAdd", "args": ["camic", "request"]} + ] + },{ + "route":"/data/Request/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Editor", "Admin"]]}, + {"function":"mongoFind", "args": ["camic", "request"]}, + {"function":"editHandler", "args": ["data", "userFilter", "filter"]}, + {"function":"mongoDelete", "args": ["camic", "request"]} + ] + }, + + { + "route":"/data/Mark/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "mark"]} + ] + },{ + "route":"/data/Mark/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "mark"]} + ] + },{ + "route":"/data/Mark/delete", + "method":"delete", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "mark"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "mark"]} + ] + },{ + "route":"/data/Mark/update", + "method":"post", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "mark"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "mark"]} + ] + },{ + "route":"/data/Mark/types", + "method":"get", + "handlers":[ + {"function":"mongoDistinct", "args": ["camic", "mark", "provenance.analysis"]} + ] + },{ + "route":"/data/Mark/multi", + "method":"post", + "handlers":[ + {"function":"markMulti", "args": []} + ] + },{ + "route":"/data/Mark/spatial", + "method":"get", + "handlers":[ + {"function":"markSpatial", "args": []} + ] + },{ + "route":"/data/Mark/findMarkTypes", + "method":"get", + "handlers":[ + {"function":"findMarkTypes", "args": []} + ] + },{ + "route":"/data/Mark/updateMarksLabel", + "method":"post", + "handlers":[ + {"function":"updateMarksLabel", "args": []} + ] + },{ + "route":"/data/Presetlabels/add", + "method":"post", + "handlers":[ + {"function":"addPresetlabels", "args": []} + ] + },{ + "route":"/data/Presetlabels/update", + "method":"post", + "handlers":[ + {"function":"updatePresetlabels", "args": []} + ] + },{ + "route":"/data/Presetlabels/remove", + "method":"post", + "handlers":[ + {"function":"removePresetlabels", "args": []} + ] + }, + { + "route":"/data/Template/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "template"]} + ] + },{ + "route":"/data/Template/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "template"]} + ] + },{ + "route":"/data/Template/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "template"]} + ] + },{ + "route":"/data/Template/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin"]]}, + {"function":"mongoUpdate", "args": ["camic", "template"]} + ] + }, + + { + "route":"/data/Heatmap/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmap"]} + ] + },{ + "route":"/data/Heatmap/types", + "method":"get", + "handlers":[ + {"function":"heatmapTypes", "args": []} + ] + },{ + "route":"/data/Heatmap/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "heatmap"]} + ] + },{ + "route":"/data/Heatmap/delete", + "method":"delete", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmap"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "heatmap"]} + ] + },{ + "route":"/data/Heatmap/update", + "method":"post", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmap"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "heatmap"]} + ] + }, + + { + "route":"/data/HeatmapEdit/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmapEdit"]} + ] + },{ + "route":"/data/HeatmapEdit/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "heatmapEdit"]} + ] + },{ + "route":"/data/HeatmapEdit/delete", + "method":"delete", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmapEdit"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "heatmapEdit"]} + ] + },{ + "route":"/data/HeatmapEdit/update", + "method":"post", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "heatmapEdit"]}, + {"function":"editHandler", "args": ["data"]}, + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "heatmapEdit"]} + ] + }, + + { + "route":"/data/Log/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "log"]} + ] + },{ + "route":"/data/Log/post", + "method":"post", + "handlers":[ + {"function":"mongoAdd", "args": ["camic", "log"]} + ] + },{ + "route":"/data/Log/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "log"]} + ] + },{ + "route":"/data/Log/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "log"]} + ] + }, + + { + "route":"/data/Evaluation/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "evaluation"]} + ] + },{ + "route":"/data/Evaluation/post", + "method":"post", + "handlers":[ + {"function":"mongoAdd", "args": ["camic", "evaluation"]} + ] + },{ + "route":"/data/Evaluation/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "evaluation"]} + ] + },{ + "route":"/data/Evaluation/update", + "method":"post", + "handlers":[ + {"function":"mongoUpdate", "args": ["camic", "evaluation"]} + ] + }, + + { + "route":"/data/Freeform/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "freeform"]} + ] + },{ + "route":"/data/Freeform/post", + "method":"post", + "handlers":[ + {"function":"mongoAdd", "args": ["camic", "freeform"]} + ] + },{ + "route":"/data/Freeform/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "freeform"]} + ] + },{ + "route":"/data/Freeform/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "freeform"]} + ] + }, + + { + "route":"/data/Configuration/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "configuration"]} + ] + },{ + "route":"/data/Configuration/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "configuration"]} + ] + },{ + "route":"/data/Configuration/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "configuration"]} + ] + },{ + "route":"/data/Configuration/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "configuration"]} + ] + },{ + "route":"/data/Collection/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "collection"]} + ] + },{ + "route":"/data/Collection/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoAdd", "args": ["camic", "collection"]} + ] + },{ + "route":"/data/Collection/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoUpdate", "args": ["camic", "collection"]} + ] + },{ + "route":"/data/Collection/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"mongoDelete", "args": ["camic", "collection"]} + ] + }, + + { + "route":"/data/User/find", + "method":"get", + "handlers":[ + {"function":"mongoFind", "args": ["camic", "user"]} + ] + },{ + "route":"/data/User/post", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin"]]}, + {"function":"mongoAdd", "args": ["camic", "user"]} + ] + },{ + "route":"/data/User/delete", + "method":"delete", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin"]]}, + {"function":"mongoDelete", "args": ["camic", "user"]} + ] + },{ + "route":"/data/User/update", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin"]]}, + {"function":"mongoUpdate", "args": ["camic", "user"]} + ] + },{ + "route":"/data/User/wcido", + "method":"get", + "handlers":[ + {"function":"wcido", "args": []} + ] + },{ + "route":"/workbench/uploadDataset", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"getDataset", "args": []} + ] + },{ + "route":"/workbench/trainModel", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"trainModel", "args": []} + ] + },{ + "route":"/workbench/deleteUserData", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"deleteDataset", "args": []} + ] + },{ + "route":"/workbench/modelDownload", + "method":"post", + "handlers":[ + {"function":"permissionHandler", "args": [["Admin", "Editor"]]}, + {"function":"sendTrainedModel", "args": []} + ] + } +] diff --git a/service/database/index.js b/service/database/index.js index 836b137..81e9a97 100644 --- a/service/database/index.js +++ b/service/database/index.js @@ -7,171 +7,204 @@ const { transformIdToObjectId } = require("./util"); * 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 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 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 + * @param {bool} silent Set true to ignore all errors + * + * {@link https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/ Read MongoDB Reference} + */ + static async add(database, collectionName, data, silent) { + /** 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) { + if (silent) { + console.warn('insert into ' + collectionName + ' did not occur, continuing because silent set') + } else { + 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]; + + /** + * 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.deleteMany(filter); + delete result.connection; + + return result; + } catch (e) { + console.error(e); + throw e; + } } - try { - const collection = getConnection(database).collection(collectionName); - const res = await collection.insertMany(data); - return res; - } 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 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.deleteMany(filter); - delete result.connection; - - 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.updateMany(filter, updates); + 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; + + static async createIndex(database, collectionName, index, unique) { + try { + const collection = getConnection(database).collection(collectionName); + const result = await collection.createIndex(index, unique); + delete result.connection; + 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.updateMany(filter, updates); - delete result.connection; - return result; - } catch (e) { - console.error(e); - throw e; + + static async createCollection(database, collectionName, validator, silent) { + try { + const collection = await getConnection(database).createCollection(collectionName, validator); + return collection; + } catch (e) { + if (silent) { + console.warn('collection creation of ' + collectionName + ' did not occur, continuing because silent set') + } else { + 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, + add: Mongo.add, + find: Mongo.find, + update: Mongo.update, + delete: Mongo.delete, + aggregate: Mongo.aggregate, + distinct: Mongo.distinct, + createIndex: Mongo.createIndex, + createCollection: Mongo.createCollection };