From 1f6c0d7dfbf3197a02289f748d2055a985ee9dcb Mon Sep 17 00:00:00 2001 From: "Dr. Asis hallab" Date: Thu, 27 Sep 2018 10:31:18 +0200 Subject: [PATCH 001/125] Sequelize now uses the config file --- connection.js | 19 +++++++---------- server.js | 57 ++++++++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/connection.js b/connection.js index 054bcdd..331c6b8 100644 --- a/connection.js +++ b/connection.js @@ -1,6 +1,10 @@ -Sequelize = require('sequelize'); +const env = process.env.NODE_ENV || 'development'; +const path = require('path') +const config = require(path.join(__dirname, 'config', 'config.json'))[env]; +const Sequelize = require('sequelize'); + const Op = Sequelize.Op; -const operatorsAliases = { +config.operatorsAliases = { $eq: Op.eq, $and: Op.and, $or: Op.or, @@ -9,15 +13,6 @@ const operatorsAliases = { $in: Op.in }; -sequelize = new Sequelize( - 'test_code_gen', - 'test_code_gen', - 'test_code_gen', - { - dialect: 'postgres', - host: '127.0.0.1' - }, - {operatorsAliases} -); +sequelize = new Sequelize(config); module.exports = sequelize; diff --git a/server.js b/server.js index 3d1706c..d49e7de 100644 --- a/server.js +++ b/server.js @@ -2,17 +2,20 @@ var path = require('path'); var graphqlHTTP = require('express-graphql'); const fileUpload = require('express-fileupload'); - var {buildSchema} = require('graphql'); + var { + buildSchema + } = require('graphql'); var mergeSchema = require('./utils/merge-schemas'); var acl = null; var cors = require('cors'); /* Temporary solution: acl rules set */ - if(process.argv.length > 2 && process.argv[2]=='acl') - { + if (process.argv.length > 2 && process.argv[2] == 'acl') { var node_acl = require('acl'); - var {aclRules} = require('./acl_rules'); + var { + aclRules + } = require('./acl_rules'); var acl = new node_acl(new node_acl.memoryBackend()); /* set authorization rules from file acl_rules.js */ @@ -22,16 +25,18 @@ /*For testing purposes*/ acl.addUserRoles(1, 'guest'); acl.addUserRoles(2, 'administrator'); -}else{ - console.log("Open server, no authorization rules"); -} + } else { + console.log("Open server, no authorization rules"); + } /* Schema */ -var merged_schema = mergeSchema( path.join(__dirname, './schemas')); -var Schema = buildSchema(merged_schema); +console.log('Merging Schema') + var merged_schema = mergeSchema(path.join(__dirname, './schemas')); +console.log(merged_schema) + var Schema = buildSchema(merged_schema); -/* Resolvers*/ -var resolvers = require('./resolvers/index'); + /* Resolvers*/ + var resolvers = require('./resolvers/index'); /* Server */ const APP_PORT = 3000; @@ -39,23 +44,23 @@ var resolvers = require('./resolvers/index'); //app.use((req, res, next)=> { - // Website you wish to allow to connect - //res.setHeader('Access-Control-Allow-Origin', '*'); - //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); + // Website you wish to allow to connect + //res.setHeader('Access-Control-Allow-Origin', '*'); + //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); - // Request methods you wish to allow - //res.setHeader('Access-Control-Allow-Methods', - // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + // Request methods you wish to allow + //res.setHeader('Access-Control-Allow-Methods', + // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - // Request headers you wish to allow - //res.setHeader('Access-Control-Allow-Headers', - // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); - // next(); - //}); + // Request headers you wish to allow + //res.setHeader('Access-Control-Allow-Headers', + // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); + // next(); + //}); -app.use(fileUpload()); + app.use(fileUpload()); /*request is passed as context by default */ - app.use('/graphql', cors(),graphqlHTTP((req)=> ({ + app.use('/graphql', cors(), graphqlHTTP((req) => ({ schema: Schema, rootValue: resolvers, pretty: true, @@ -67,8 +72,8 @@ app.use(fileUpload()); }))); -var server = app.listen(APP_PORT, ()=>{ + var server = app.listen(APP_PORT, () => { console.log(`App listening on port ${APP_PORT}`); }); -module.exports = server; + module.exports = server; From f353fc2d1fa249ace4908e3e95684254697e7c60 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 27 Sep 2018 09:53:53 -0500 Subject: [PATCH 002/125] no test specified --- package.json | 2 +- test/test-data.js | 60 ----------------------------- test/test.js | 97 ----------------------------------------------- 3 files changed, 1 insertion(+), 158 deletions(-) delete mode 100644 test/test-data.js delete mode 100644 test/test.js diff --git a/package.json b/package.json index a1488db..79dd614 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Schema as string", "main": "index.js", "scripts": { - "test": "mocha test/test.js --exit" + "test": "echo \"Error: no test specified\" && exit 1" }, "author": "vsuaste", "license": "ISC", diff --git a/test/test-data.js b/test/test-data.js deleted file mode 100644 index 47ac566..0000000 --- a/test/test-data.js +++ /dev/null @@ -1,60 +0,0 @@ - -module.exports.people = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - "email": "leonardo.vinci@art.com" - }, - { - "firstName": "Thomas", - "lastName": "Edison", - "email": "thomas.edison@science.com" - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - "email": "vicent.vanGogh@art.com" - }, - { - "firstName": "Albert", - "lastName": "Einstein", - "email": "albert.einstein@science.com" - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - "email": "ludwig.beethoven@art.com" - } - ]; - -module.exports.searchPeopleByEmail = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - } -]; - -module.exports.readOneBook = { - "title": "Science of music" - }; - - -module.exports.addDog = { - "name": "toto", - "breed": "chihuahua" - }; - -module.exports.deleteDog = "Item succesfully deleted"; - -module.exports.updateBook = { - "title": "Paintings II", - "genre": "Art" -}; diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 58ca736..0000000 --- a/test/test.js +++ /dev/null @@ -1,97 +0,0 @@ -var server = require('../server'); -const request = require('supertest'); -const test = require('./test-data'); - -const chai = require('chai'); -const expect = chai.expect; - -describe('Testing Queries Server GraphQL', ()=>{ - it('Read all - people', (done)=>{ - request(server).post('/graphql') - .send({query: '{people{ firstName lastName email}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.people).to.deep.equal(test.people); - done(); - }); - - }); - - it('Search with filter - people by email', (done)=>{ - request(server).post('/graphql') - .send({query: '{ searchPerson(input:{field:email, value:{value:"%art%"}, operator:like}){ firstName lastName}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.searchPerson).to.deep.equal(test.searchPeopleByEmail); - done(); - }); - - }); - - it('Read one by Id - books', (done)=>{ - request(server).post('/graphql') - .send({query: '{readOneBook(id:3){title}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.readOneBook).to.deep.equal(test.readOneBook); - done(); - }); - }); -}); - -describe('Testing Mutations Server GraphQL', ()=>{ - it('Create one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{addDog(name:"toto", breed:"chihuahua"){name breed}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.addDog).to.deep.equal(test.addDog); - done(); - }); - }); - - it('Delete one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{deleteDog(id:6)}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.deleteDog).to.deep.equal(test.deleteDog); - done(); - }); - }); - - it('Update one - books', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{updateBook(id:2, title:"Paintings II"){title genre}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.updateBook).to.deep.equal(test.updateBook); - done(); - }); - }); - - -}); - - - - -server.close(); From 743b494607d28d297df6aeef92144b8739d5f767 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 5 Oct 2018 16:41:05 -0500 Subject: [PATCH 003/125] environmental limit for resolvers --- config/globals.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 config/globals.js diff --git a/config/globals.js b/config/globals.js new file mode 100644 index 0000000..9a44676 --- /dev/null +++ b/config/globals.js @@ -0,0 +1,3 @@ +module.exports = { + LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000 +} From 4f418c5e6b63fd1bb9e6ac702dc61a913ef762c1 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 9 Oct 2018 12:06:38 -0500 Subject: [PATCH 004/125] change search argument name --- utils/search-argument.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/utils/search-argument.js b/utils/search-argument.js index bb69e50..c93e6d8 100644 --- a/utils/search-argument.js +++ b/utils/search-argument.js @@ -2,13 +2,13 @@ Class to parse search argument for any model */ -module.exports = class searchArg{ +module.exports = class search{ - constructor({field, value, operator, searchArg}){ + constructor({field, value, operator, search}){ this.field = field; this.value = this.constructor.parseValue(value); this.operator = operator; - this.searchArg = searchArg + this.search = search } static parseValue(val){ @@ -24,31 +24,31 @@ module.exports = class searchArg{ } toSequelize(){ - let searchArgsInSequelize = {}; + let searchsInSequelize = {}; - if(this.searchArg === undefined && this.field === undefined) + if(this.search === undefined && this.field === undefined) { - searchArgsInSequelize['$'+this.operator] = this.value; + searchsInSequelize['$'+this.operator] = this.value; - }else if(this.searchArg === undefined) + }else if(this.search === undefined) { - searchArgsInSequelize[this.field] = { + searchsInSequelize[this.field] = { ['$'+this.operator] : this.value }; }else if(this.field === undefined){ - searchArgsInSequelize['$'+this.operator] = this.searchArg.map(sa => { - let new_sa = new searchArg(sa); + searchsInSequelize['$'+this.operator] = this.search.map(sa => { + let new_sa = new search(sa); return new_sa.toSequelize(); }); }else{ - searchArgsInSequelize[this.field] = { - ['$'+this.operator] : this.searchArg.map(sa => { - let new_sa = new searchArg(sa); + searchsInSequelize[this.field] = { + ['$'+this.operator] : this.search.map(sa => { + let new_sa = new search(sa); return new_sa.toSequelize(); }) } } - return searchArgsInSequelize; + return searchsInSequelize; } }; From 16dc3f9ee6963c8666acd5804603403fb48b456c Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 10 Oct 2018 16:27:46 -0500 Subject: [PATCH 005/125] helper for vue table creation --- package-lock.json | 55 +++++++++++++++++++++ package.json | 1 + utils/helper.js | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 utils/helper.js diff --git a/package-lock.json b/package-lock.json index 0ff79f5..9a06850 100644 --- a/package-lock.json +++ b/package-lock.json @@ -315,6 +315,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, + "complex.js": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", + "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -439,6 +444,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decimal.js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", + "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -611,6 +621,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-latex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.1.1.tgz", + "integrity": "sha512-N2D6Z2kXh8x/pQNQH+natXDCwrzghhXMRII5dZ518mlTLeuba80NL0LCQyaahqOrAidoLivmmG6GKPnGhHse+A==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -778,6 +793,11 @@ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" }, + "fraction.js": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.9.tgz", + "integrity": "sha512-qP1sNwdrcA+Vs5TTvGETuaaUmz4Tm48V6Jc+8Oh/gqvkb1d42s99w5kvSrZkIATp/mz3rV4CTef6xINkCofu+A==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -998,6 +1018,11 @@ "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "js-beautify": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", @@ -1151,6 +1176,21 @@ "es5-ext": "0.10.42" } }, + "mathjs": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-5.2.0.tgz", + "integrity": "sha512-TwiegJ/k9zXRhyYFjeuBB5Nmw+GXDq4pRC9bcRhRlq654DZLv4QAvp24l9dEk2G2QiU1PfzvP1dd/CCEqsMZ+w==", + "requires": { + "complex.js": "2.0.11", + "decimal.js": "10.0.1", + "escape-latex": "1.1.1", + "fraction.js": "4.0.9", + "javascript-natural-sort": "0.7.1", + "seed-random": "2.2.0", + "tiny-emitter": "2.0.2", + "typed-function": "1.1.0" + } + }, "md5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", @@ -1716,6 +1756,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" + }, "semver": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", @@ -2033,6 +2078,11 @@ "next-tick": "1.0.0" } }, + "tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -2052,6 +2102,11 @@ "mime-types": "2.1.18" } }, + "typed-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.0.tgz", + "integrity": "sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA==" + }, "umzug": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz", diff --git a/package.json b/package.json index 79dd614..75888aa 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "graphql": "^0.13.1", "jsonwebtoken": "^8.2.0", "lodash": "^4.17.5", + "mathjs": "^5.2.0", "merge-graphql-schemas": "^1.5.1", "pg": "^7.4.1", "pg-hstore": "^2.3.2", diff --git a/utils/helper.js b/utils/helper.js new file mode 100644 index 0000000..eef6794 --- /dev/null +++ b/utils/helper.js @@ -0,0 +1,122 @@ +const objectAssign = require('object-assign'); +const math = require('mathjs'); + + paginate = function(req) { + selectOpts = {} + if (req.query.per_page){ selectOpts['limit'] = req.query.per_page} + else{ selectOpts['limit'] = 20} + if (req.query.page) { + os = (req.query.page - 1) * selectOpts['limit'] + selectOpts['offset'] = os + } + return selectOpts + } + + requestedUrl = function(req) { + //console.log(req.port) + //console.log(req.headers.host) + //let port = req.port|| 2000; + return req.protocol + '://' + req.headers.host + + //(port == 80 || port == 443 ? '' : ':' + port) + + req.baseUrl; + } + + + prevNextPageUrl = function(req, isPrevious) { + //console.log("Requested URL", req); + let baseUrl = requestedUrl(req).replace(/\?.*$/, '') + let query = ["query="+req.query.query] + i = isPrevious ? -1 : 1 + // page + p = req.query.page == '1' ? null : (req.query.page + i) + query = query.concat(['page=' + p]) + // per_page + query = query.concat(['per_page=' + (req.query.per_page || 20)]) + // filter + if (req.query.filter) query = query.concat(['filter=' + req.query.filter]) + // sort + if (req.query.sort) query = query.concat(['sort=' + req.query.sort]) + // Append query to base URL + if (query.length > 0) baseUrl += "?" + query.join("&") + return baseUrl + } + + sort = function(req) { + let sortOpts = {} + if (req.query.sort) { + sortOpts = { + order: [req.query.sort.split('|')] + } + } + return sortOpts + } + + search = function(req, strAttributes) { + let selectOpts = {} + if (req.query.filter) { + let fieldClauses = [] + strAttributes.forEach(function(x) { + let fieldWhereClause = {} + if (x !== "id") { + fieldWhereClause[x] = { + $like: "%" + req.query.filter + "%" + } + fieldClauses = fieldClauses.concat([fieldWhereClause]) + } else { + if (/^\d+$/.test(req.query.filter)) { + fieldWhereClause[x] = req.query.filter + fieldClauses = fieldClauses.concat([fieldWhereClause]) + } + } + }) + selectOpts['where'] = { + $or: fieldClauses + } + } + return selectOpts + } + + +includeAssociations = function (req) { + return req.query.excludeAssociations ? {} : { + include: [{ + all: true + }] + } +} + +searchPaginate = function(req, strAttributes) { + return objectAssign( + search(req, strAttributes), + sort(req), + paginate(req), + includeAssociations(req) + ); +} + +module.exports.vueTable = function(req, model, strAttributes) { + let searchOptions = search(req, strAttributes) + let searchSortPagIncl = searchPaginate( req, strAttributes ) + let queries = [] + queries.push(model.count(searchOptions)) + queries.push(model.findAll(searchSortPagIncl)) + return Promise.all(queries).then( + function(res) { + let searchRes = res[0] + let paginatedSearchRes = res[1] + let lastPage = math.ceil(searchRes / req.query.per_page) + return { + data: paginatedSearchRes, + total: searchRes, + per_page: req.query.per_page, + current_page: req.query.page, + 'from': (req.query.page - 1) * req.query.per_page + 1, + 'to': req.query.page * req.query.per_page, + last_page: lastPage, + prev_page_url: (req.query.page == 1) ? null : prevNextPageUrl( + req, true), + next_page_url: (req.query.page == lastPage) ? null : prevNextPageUrl( + req, false) + } + }) + } From a3396353d894e32d5a99b0e6f0e02e7d60b00ca5 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 22 Oct 2018 13:24:35 -0500 Subject: [PATCH 006/125] no include all association by default --- utils/helper.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/helper.js b/utils/helper.js index eef6794..700dd11 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -86,11 +86,12 @@ includeAssociations = function (req) { } searchPaginate = function(req, strAttributes) { + console.log("New changes applied including all"); return objectAssign( search(req, strAttributes), sort(req), - paginate(req), - includeAssociations(req) + paginate(req) + //,includeAssociations(req) ); } From a9f911275db712bbde1e47b06e06886a4f67ce21 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 22 Oct 2018 13:24:52 -0500 Subject: [PATCH 007/125] no include all association by default --- utils/helper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/helper.js b/utils/helper.js index 700dd11..462dd2a 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -86,7 +86,6 @@ includeAssociations = function (req) { } searchPaginate = function(req, strAttributes) { - console.log("New changes applied including all"); return objectAssign( search(req, strAttributes), sort(req), From c8660f3875752dab514d95e04df605d739ba97b6 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 23 Oct 2018 11:57:01 -0500 Subject: [PATCH 008/125] fixed numeration for vueTable resolver --- utils/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/helper.js b/utils/helper.js index 462dd2a..a3a1434 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -111,7 +111,7 @@ module.exports.vueTable = function(req, model, strAttributes) { per_page: req.query.per_page, current_page: req.query.page, 'from': (req.query.page - 1) * req.query.per_page + 1, - 'to': req.query.page * req.query.per_page, + 'to': math.min(searchRes, req.query.page * req.query.per_page), last_page: lastPage, prev_page_url: (req.query.page == 1) ? null : prevNextPageUrl( req, true), From 776620244693c10470e9b42f0da775cc91789931 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 22 Nov 2018 20:34:39 -0600 Subject: [PATCH 009/125] stream csv parser --- package-lock.json | 26 ++++++++++++++++++++++ package.json | 2 ++ utils/file-tools.js | 54 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a06850..95dcef4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "awaitify-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/awaitify-stream/-/awaitify-stream-1.0.2.tgz", + "integrity": "sha512-JE6mrRIPxhBQWt9Mu4u2XJF9V9xp4p5+Uxif/Ol/s2TFC/9+offUE50j/KQdaVWwMyGlKrlsE7Ncq/yhc6AJ+w==" + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -1861,6 +1866,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "sha-1": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sha-1/-/sha-1-0.1.1.tgz", + "integrity": "sha1-KjkwS/QburEd2e+3R07CWxqSwlc=" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -2148,6 +2158,22 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, + "uuidv4": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-2.0.0.tgz", + "integrity": "sha512-sAUlwUVepcVk6bwnaW/oi6LCwMdueako5QQzRr90ioAVVcms6p1mV0PaSxK8gyAC4CRvKddsk217uUpZUbKd2Q==", + "requires": { + "sha-1": "0.1.1", + "uuid": "3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "validate-npm-package-license": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", diff --git a/package.json b/package.json index 75888aa..26114b6 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "acl": "^0.4.11", + "awaitify-stream": "^1.0.2", "bcryptjs": "^2.4.3", "chai": "^4.1.2", "cors": "^2.8.4", @@ -28,6 +29,7 @@ "sequelize": "^4.35.2", "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", + "uuidv4": "^2.0.0", "xlsx": "^0.12.11" }, "devDependencies": { diff --git a/utils/file-tools.js b/utils/file-tools.js index 931f00f..0c1ac5b 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -1,6 +1,10 @@ const XLSX = require('xlsx'); const Promise = require('bluebird'); -const csv_parse = Promise.promisify(require('csv-parse')); +const promise_csv_parse = Promise.promisify(require('csv-parse')); +const csv_parse = require('csv-parse'); +const fs = require('fs') +const awaitifyStream = require('awaitify-stream') + replaceNullStringsWithLiteralNulls = function(arrOfObjs) { console.log(typeof arrOfObjs, arrOfObjs); @@ -18,7 +22,7 @@ exports.parseCsv = function(csvStr, delim, cols) { if (!delim) delim = "," if (typeof cols === 'undefined') cols = true return replaceNullStringsWithLiteralNulls( - csv_parse(csvStr, { + promise_csv_parse(csvStr, { delimiter: delim, columns: cols }) @@ -35,3 +39,49 @@ exports.parseXlsx = function(bstr) { workbook.Sheets[sheet_name_list[0]]) ); } + +/** +* Parse a csv file and create the records in the correspondant table +* @function +* @param {string} csvFilePath - The path where the csv file is stored +* @param {} model - Sequelize model, record will be created through this model +* @param {string} delim - Set the field delimiter in the csv file. One or multiple character. +* @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line) +*/ +exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { + if (!delim) delim = "," + if (typeof cols === 'undefined') cols = true + console.log("TYPEOF", typeof model) + // Wrap all database actions within a transaction: + let transaction = await model.sequelize.transaction() + try { + // Pipe a file read-stream through a CSV-Reader and make the records + // handleable asynchronously: + let csvStream = awaitifyStream.createReader( + fs.createReadStream(csvFilePath).pipe( + csv_parse({ + delimiter: delim, + columns: cols + }) + ) + ) + + let record + while (null !== (record = await csvStream.readAsync())) { + console.log(`Read record: ${JSON.stringify(record)}`) + await model.create(record, { + transaction: transaction + }).catch(error => { + console.log(`Caught error in while-loop: ${JSON.stringify(error)}`) + // Enable identification of those rows / records that caused validation + // errors: + error.record = record + throw error + }) + } + await transaction.commit() + } catch (error) { + await transaction.rollback() + throw error + } +} From 241a58ce02820a83a35efd3b91b69d21303e9a81 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Sun, 25 Nov 2018 14:16:55 -0600 Subject: [PATCH 010/125] Ignore all automatically generated files --- .gitignore | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3900f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# IntelliJ Idea project settings folder +.idea + +# NPM package manager temporary files and folders +node_modules +npm-debug.log +package-lock.json + +# ModelGen output folders +migrations +models +models-webservice +resolvers +schemas \ No newline at end of file From 9ef21d49b98b4c44a8f03fb37b17ab072dd8313c Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Sun, 25 Nov 2018 14:41:12 -0600 Subject: [PATCH 011/125] start/test scripts added --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 26114b6..e08e765 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Schema as string", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node server.js", + "test": "mocha test/test.js --exit" }, "author": "vsuaste", "license": "ISC", From 96dbec7020f6141d0128e8e7e04884b8064636ef Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Sun, 25 Nov 2018 14:42:17 -0600 Subject: [PATCH 012/125] Testing scripts copied from vsuaste repo --- test/test-data.js | 59 ++++++++++++++++++++++++++++ test/test.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 test/test-data.js create mode 100644 test/test.js diff --git a/test/test-data.js b/test/test-data.js new file mode 100644 index 0000000..1578683 --- /dev/null +++ b/test/test-data.js @@ -0,0 +1,59 @@ +module.exports.people = [ + { + "firstName": "Leonardo", + "lastName": "Da Vinci", + "email": "leonardo.vinci@art.com" + }, + { + "firstName": "Thomas", + "lastName": "Edison", + "email": "thomas.edison@science.com" + }, + { + "firstName": "Vicent", + "lastName": "van Gogh", + "email": "vicent.vanGogh@art.com" + }, + { + "firstName": "Albert", + "lastName": "Einstein", + "email": "albert.einstein@science.com" + }, + { + "firstName": "Ludwig", + "lastName": "Beethoven", + "email": "ludwig.beethoven@art.com" + } +]; + +module.exports.searchPeopleByEmail = [ + { + "firstName": "Leonardo", + "lastName": "Da Vinci", + }, + { + "firstName": "Vicent", + "lastName": "van Gogh", + }, + { + "firstName": "Ludwig", + "lastName": "Beethoven", + } +]; + +module.exports.readOneBook = { + "title": "Science of music" +}; + + +module.exports.addDog = { + "name": "toto", + "breed": "chihuahua" +}; + +module.exports.deleteDog = "Item succesfully deleted"; + +module.exports.updateBook = { + "title": "Paintings II", + "genre": "Art" +}; \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..d2c419e --- /dev/null +++ b/test/test.js @@ -0,0 +1,97 @@ +var server = require('../server'); +const request = require('supertest'); +const test = require('./test-data'); + +const chai = require('chai'); +const expect = chai.expect; + +describe('Testing Queries Server GraphQL', ()=>{ + it('Read all - people', (done)=>{ + request(server).post('/graphql') + .send({query: '{people{ firstName lastName email}}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.people).to.deep.equal(test.people); + done(); + }); + + }); + + it('Search with filter - people by email', (done)=>{ + request(server).post('/graphql') + .send({query: '{ searchPerson(input:{field:email, value:{value:"%art%"}, operator:like}){ firstName lastName}}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.searchPerson).to.deep.equal(test.searchPeopleByEmail); + done(); + }); + + }); + + it('Read one by Id - books', (done)=>{ + request(server).post('/graphql') + .send({query: '{readOneBook(id:3){title}}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.readOneBook).to.deep.equal(test.readOneBook); + done(); + }); + }); +}); + +describe('Testing Mutations Server GraphQL', ()=>{ + it('Create one - dogs', (done)=>{ + request(server).post('/graphql') + .send({query: 'mutation{addDog(name:"toto", breed:"chihuahua"){name breed}}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.addDog).to.deep.equal(test.addDog); + done(); + }); + }); + + it('Delete one - dogs', (done)=>{ + request(server).post('/graphql') + .send({query: 'mutation{deleteDog(id:6)}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.deleteDog).to.deep.equal(test.deleteDog); + done(); + }); + }); + + it('Update one - books', (done)=>{ + request(server).post('/graphql') + .send({query: 'mutation{updateBook(id:2, title:"Paintings II"){title genre}}'}) + .expect(200).end((err,res)=>{ + if(err){ + console.log(err); + done(err); + } + expect(res.body.data.updateBook).to.deep.equal(test.updateBook); + done(); + }); + }); + + +}); + + + + +server.close(); \ No newline at end of file From 1bb246cc26412dd11c4d229bd042a39c9847607b Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Wed, 28 Nov 2018 18:48:33 -0600 Subject: [PATCH 013/125] mysql2 dependency added --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e08e765..ab293d6 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", "uuidv4": "^2.0.0", - "xlsx": "^0.12.11" + "xlsx": "^0.12.11", + "mysql2": "^1.6.4" }, "devDependencies": { "mocha": "^5.2.0" From 90e528d298d75b8cea223bf6bcbc7a62b065902f Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Wed, 28 Nov 2018 23:08:09 -0600 Subject: [PATCH 014/125] mysql2 dependency added --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6aa2032..fe2e81e 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,20 @@ This package integrates the code generated by [code-generator](https://github.co After getting ready the generated code for the models, proceed with the server set up. ## Set up + +Install the mysql database and configure it: +``` +$ mysqladmin -u root -p'' password '' +$ mysql -u root -e "create database database_development" +``` + Clone the repository and run: ``` $ npm install -$ node_modules/.bin/sequelize db:migrate -$ node server.js +$ sd="node_modules/.bin/" +$ ${sd}/sequelize init +$ ${sd}/sequelize db:migrate +$ npm start ``` ``` $ node_modules/.bin/sequelize db:migrate ``` command will create the tables specified in the ```migrations``` folder. From 895a9caabb92e2065b53583b8824c826805dcf68 Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Mon, 3 Dec 2018 18:35:56 -0600 Subject: [PATCH 015/125] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe2e81e..9d5d28b 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' We'll search people with 'science' as substring of their email and as result we'll get only their name and last name. ``` -curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' -d '{ searchPerson(input:{field:email, value:{value:"%science%"}, operator:like}){ firstName lastName}}' +curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' -d '{ people(search:{field:email, value:{value:"%science%"}, operator:like}){ firstName lastName}}' ``` The result will be: From d3533807779be40be30d6de1cafbde0477ad730c Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Wed, 19 Dec 2018 13:46:13 -0600 Subject: [PATCH 016/125] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d5d28b..b7fb696 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # GraphQL backend Skeleton NodeJS project for a graphQL server. -This package integrates the code generated by [code-generator](https://github.com/vsuaste/express_graphql_model_gen). +This package integrates the code generated by [code-generator](https://github.com/ScienceDb/graphql-server-model-codegen). -[Code-generator](https://github.com/vsuaste/express_graphql_model_gen) will generate four folders with the models' information: +[Code-generator](https://github.com/ScienceDb/graphql-server-model-codegen) will generate four folders with the models' information: * models * schemas * resolvers @@ -32,7 +32,7 @@ $ npm start With credential as in ``` config/config.json``` file. ## Example of use -If you followed the example for generating the code described [here](https://github.com/vsuaste/express_graphql_model_gen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. +If you followed the example for generating the code described [here](https://github.com/ScienceDb/graphql-server-model-codegen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. We will add the next 4 people to our table ``people``. From 8cb29b13892e5a62ae6da0aa2153bce69715c53d Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Wed, 19 Dec 2018 13:52:44 -0600 Subject: [PATCH 017/125] Update README.md --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index b7fb696..b6b6e2c 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,10 @@ After getting ready the generated code for the models, proceed with the server s ## Set up -Install the mysql database and configure it: -``` -$ mysqladmin -u root -p'' password '' -$ mysql -u root -e "create database database_development" -``` - Clone the repository and run: ``` $ npm install -$ sd="node_modules/.bin/" -$ ${sd}/sequelize init -$ ${sd}/sequelize db:migrate +$ node_modules/.bin/sequelize db:migrate $ npm start ``` From 0f93a3c83431a85876fa3e32519a0bf9a3e09426 Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Wed, 19 Dec 2018 17:38:28 -0600 Subject: [PATCH 018/125] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b6b6e2c..6210027 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ $ npm start ``` $ node_modules/.bin/sequelize db:migrate ``` command will create the tables specified in the ```migrations``` folder. With credential as in ``` config/config.json``` file. +## NOTE +A data base should alreade be configured locally as in `config/config.json` + ## Example of use If you followed the example for generating the code described [here](https://github.com/ScienceDb/graphql-server-model-codegen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. From c0c251e89014011cb18a63c1c6f76a2b422c2a25 Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Wed, 19 Dec 2018 17:39:35 -0600 Subject: [PATCH 019/125] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6210027..23558c9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $ npm start With credential as in ``` config/config.json``` file. ## NOTE -A data base should alreade be configured locally as in `config/config.json` +A data base should be already configured locally as in `config/config.json` ## Example of use If you followed the example for generating the code described [here](https://github.com/ScienceDb/graphql-server-model-codegen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. From ae2b516f6634cefad27b766aa0495a409f82049a Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 7 Jan 2019 14:58:53 -0600 Subject: [PATCH 020/125] JSDoc type documentation --- package-lock.json | 87 ++++++++++++++++++++++++++++++++++++ utils/check-authorization.js | 10 +++++ utils/file-tools.js | 23 ++++++++-- utils/helper.js | 68 +++++++++++++++++++++++++--- utils/merge-schemas.js | 7 +++ utils/search-argument.js | 29 ++++++++++-- 6 files changed, 210 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95dcef4..dbbbd3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -472,6 +472,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "denque": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.3.0.tgz", + "integrity": "sha512-4SRaSj+PqmrS1soW5/Avd7eJIM2JJIqLLmwhRqIGleZM/8KwZq80njbSS2Iqas+6oARkSkLDHEk4mm78q3JlIg==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -823,6 +828,14 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "1.0.2" + } + }, "generic-pool": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", @@ -1003,6 +1016,11 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -1165,6 +1183,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "lru-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", @@ -1365,6 +1388,55 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mysql2": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.6.4.tgz", + "integrity": "sha512-ZYbYgK06HKfxU45tYYLfwW5gKt8BslfE7FGyULNrf2K2fh+DuEX+e0QKsd2ObpZkMILefaVn8hsakVsTFqravQ==", + "requires": { + "denque": "1.3.0", + "generate-function": "2.3.1", + "iconv-lite": "0.4.24", + "long": "4.0.0", + "lru-cache": "4.1.3", + "named-placeholders": "1.1.1", + "seq-queue": "0.0.5", + "sqlstring": "2.3.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": "2.1.2" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + } + } + }, + "named-placeholders": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz", + "integrity": "sha1-O3oNJiA910s6nfTJz7gnsvuQfmQ=", + "requires": { + "lru-cache": "2.5.0" + }, + "dependencies": { + "lru-cache": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz", + "integrity": "sha1-2COIrpyWC+y+oMc7uet5tsbOmus=" + } + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -1761,6 +1833,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "seed-random": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", @@ -1791,6 +1868,11 @@ "statuses": "1.3.1" } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" + }, "sequelize": { "version": "4.35.2", "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.35.2.tgz", @@ -1935,6 +2017,11 @@ "through": "2.3.8" } }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "ssf": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.2.tgz", diff --git a/utils/check-authorization.js b/utils/check-authorization.js index 5fe8dbd..1da8334 100644 --- a/utils/check-authorization.js +++ b/utils/check-authorization.js @@ -2,6 +2,16 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const secret = 'something-secret'; + +/** + * @function - Given a context this function check if the user(implicit in context) + * is allowed to perform 'permission' action to the 'resource' model. + * + * @param {object} context context object contains the request info and the acl rules. + * @param {string} resource resource to which the user wants to perform an action (i.e. a model). + * @param {string} permission action that the user wants to perform to resourse (i.e. read, edit, create). + * @return {boolean} Return true if within the context the user is allowed to perform 'permission' action to the 'resource' model. + */ module.exports = function( context, resource, permission ) { //if there's not authorization rules set if (context.acl == null) return true; diff --git a/utils/file-tools.js b/utils/file-tools.js index 0c1ac5b..43ab182 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -6,6 +6,12 @@ const fs = require('fs') const awaitifyStream = require('awaitify-stream') +/** + * replaceNullStringsWithLiteralNulls - Replace null entries of columns with literal null types + * + * @param {array} arrOfObjs Each item correponds to a column represented as object. + * @return {array} Each item corresponds to a column and all items have either a valid entry or null type. + */ replaceNullStringsWithLiteralNulls = function(arrOfObjs) { console.log(typeof arrOfObjs, arrOfObjs); return arrOfObjs.map(function(csvRow) { @@ -18,6 +24,15 @@ replaceNullStringsWithLiteralNulls = function(arrOfObjs) { }); } + +/** + * parseCsv - parse csv file (string) + * + * @param {string} csvStr Csv file converted to string. + * @param {string} delim Set the field delimiter in the csv file. One or multiple character. + * @param {array|boolean|function} cols Columns as in csv-parser options.(true if auto-discovered in the first CSV line). + * @return {array} Each item correponds to a column represented as object and filtered with replaceNullStringsWithLiteralNulls function. + */ exports.parseCsv = function(csvStr, delim, cols) { if (!delim) delim = "," if (typeof cols === 'undefined') cols = true @@ -41,12 +56,12 @@ exports.parseXlsx = function(bstr) { } /** -* Parse a csv file and create the records in the correspondant table +* Parse by streaming a csv file and create the records in the correspondant table * @function -* @param {string} csvFilePath - The path where the csv file is stored -* @param {} model - Sequelize model, record will be created through this model +* @param {string} csvFilePath - The path where the csv file is stored. +* @param {object} model - Sequelize model, record will be created through this model. * @param {string} delim - Set the field delimiter in the csv file. One or multiple character. -* @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line) +* @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). */ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { if (!delim) delim = "," diff --git a/utils/helper.js b/utils/helper.js index a3a1434..4358207 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -1,6 +1,14 @@ const objectAssign = require('object-assign'); const math = require('mathjs'); + + /** + * paginate - Creates pagination argument as needed in sequelize cotaining limit and offset accordingly to the current + * page implicit in the request info. + * + * @param {object} req Request info. + * @return {object} Pagination argument. + */ paginate = function(req) { selectOpts = {} if (req.query.per_page){ selectOpts['limit'] = req.query.per_page} @@ -12,6 +20,14 @@ const math = require('mathjs'); return selectOpts } + + + /** + * requestedUrl - Recover baseUrl from the request. + * + * @param {object} req Request info. + * @return {string} baseUrl from request. + */ requestedUrl = function(req) { //console.log(req.port) //console.log(req.headers.host) @@ -22,6 +38,14 @@ const math = require('mathjs'); } + + /** + * prevNextPageUrl - Creates request string for previous or next page int the vue-table data object. + * + * @param {object} req Request info. + * @param {boolean} isPrevious True if previous page is requestes and false if next page is requested. + * @return {string} String request for previous or next page int the vue-table data object. + */ prevNextPageUrl = function(req, isPrevious) { //console.log("Requested URL", req); let baseUrl = requestedUrl(req).replace(/\?.*$/, '') @@ -41,6 +65,13 @@ const math = require('mathjs'); return baseUrl } + + /** + * sort - Creates sort argument as needed in sequelize and accordingly to the order implicit in the resquest info. + * + * @param {object} req Request info. + * @return {object} Sort argument object as needed in the schema to retrieve filtered records from a given model. + */ sort = function(req) { let sortOpts = {} if (req.query.sort) { @@ -51,6 +82,14 @@ const math = require('mathjs'); return sortOpts } + + /** + * search - Creates search argument as needed in sequelize and accordingly to the filter string implicit in the resquest info. + * + * @param {object} req Request info. This info will contain the substring that will be used to filter records. + * @param {array} strAttributes Name of model's attributes + * @return {object} Search argument object as needed in the schema to retrieve filtered records from a given model. + */ search = function(req, strAttributes) { let selectOpts = {} if (req.query.filter) { @@ -77,14 +116,21 @@ const math = require('mathjs'); } -includeAssociations = function (req) { - return req.query.excludeAssociations ? {} : { - include: [{ - all: true - }] - } -} +// includeAssociations = function (req) { +// return req.query.excludeAssociations ? {} : { +// include: [{ +// all: true +// }] +// } +// } +/** + * searchPaginate - Creates one object mergin search, sort, and paginate arguments + * + * @param {object} req Request info. + * @param {array} strAttributes Name of model's attributes. + * @return {object} General argument for filtering models in sequelize. + */ searchPaginate = function(req, strAttributes) { return objectAssign( search(req, strAttributes), @@ -94,6 +140,14 @@ searchPaginate = function(req, strAttributes) { ); } +/** + * vueTable - Creates object needed to display a vue-table in a vuejs SPA + * + * @param {object} req Request info. + * @param {object} model Sequelize model which records are intended to be displayed in the vue-table. + * @param {array} strAttributes Name of model's attributes. + * @return {object} Info for displaying vue-table in a vuejs SPA, including info for automatic pagination. + */ module.exports.vueTable = function(req, model, strAttributes) { let searchOptions = search(req, strAttributes) let searchSortPagIncl = searchPaginate( req, strAttributes ) diff --git a/utils/merge-schemas.js b/utils/merge-schemas.js index c0ccf7b..dc2092b 100644 --- a/utils/merge-schemas.js +++ b/utils/merge-schemas.js @@ -1,6 +1,13 @@ var { fileLoader, mergeTypes } = require('merge-graphql-schemas'); + +/** + * @function - Merge graphql schemas stored in a same directory + * + * @param {string} schemas_folder path to directory where all graphql schemas are stored. + * @return {string} Merged graphql schema. + */ module.exports = function( schemas_folder ) { const typesArray = fileLoader( schemas_folder); let merged = mergeTypes(typesArray); diff --git a/utils/search-argument.js b/utils/search-argument.js index c93e6d8..d52ab95 100644 --- a/utils/search-argument.js +++ b/utils/search-argument.js @@ -1,9 +1,19 @@ -/* - Class to parse search argument for any model -*/ +/** + * search Class to parse search argument for any model and translate it so sequelize model will accept it + */ module.exports = class search{ + + /** + * constructor - Creates an instace with the given arguments + * + * @param {string} field field to filter. + * @param {object} value value contains type(i.e. array, string) and actual value to match in the filter. + * @param {string} operator operator used to perform the filter. + * @param {object} search recursive search instance. + * @return {object} instace of search class. + */ constructor({field, value, operator, search}){ this.field = field; this.value = this.constructor.parseValue(value); @@ -11,6 +21,13 @@ module.exports = class search{ this.search = search } + + /** + * @static parseValue - Creates the proper type(either array or string) of the value that user wants to filter. + * + * @param {object} val value object to parse. + * @return {(array|string|number)} Parsed value + */ static parseValue(val){ if(val!==undefined) { @@ -23,6 +40,12 @@ module.exports = class search{ } } + + /** + * toSequelize - Convert recursive search instance to search object that sequelize will accept as input. + * + * @return {object} Translated search instance into sequelize object format. + */ toSequelize(){ let searchsInSequelize = {}; From f2cdf17e5ee7c26bc2f90953de0dbf256d3ac048 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 7 Jan 2019 15:02:27 -0600 Subject: [PATCH 021/125] JSDoc type documentation --- utils/file-tools.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utils/file-tools.js b/utils/file-tools.js index 43ab182..9127a94 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -44,6 +44,13 @@ exports.parseCsv = function(csvStr, delim, cols) { ) } + +/** + * parseXlsx - description + * + * @param {string} bstr Xlsx file converted to string + * @return {array} Each item correponds to a column represented as object and filtered with replaceNullStringsWithLiteralNulls function. + */ exports.parseXlsx = function(bstr) { var workbook = XLSX.read(bstr, { type: "binary" From fdde7feecb6327af9199ffd3e10f72b430f60f7d Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 8 Jan 2019 17:53:20 +0100 Subject: [PATCH 022/125] Fixed bug 'Deprecation warning for String based operators' Also renamed config.json to sequelize_config.json, because it is more informative. --- config/config.json | 23 ----------------------- config/sequelize_config.json | 26 ++++++++++++++++++++++++++ connection.js | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) delete mode 100644 config/config.json create mode 100644 config/sequelize_config.json diff --git a/config/config.json b/config/config.json deleted file mode 100644 index 5d202e1..0000000 --- a/config/config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "development": { - "username": "test_code_gen", - "password": "test_code_gen", - "database": "test_code_gen", - "host": "127.0.0.1", - "dialect": "postgres" - }, - "test": { - "username": "test_code_gen", - "password": "test_code_gen", - "database": "test_code_gen", - "host": "127.0.0.1", - "dialect": "postgres" - }, - "production": { - "username": "test_code_gen", - "password": "test_code_gen", - "database": "test_code_gen", - "host": "127.0.0.1", - "dialect": "postgres" - } -} diff --git a/config/sequelize_config.json b/config/sequelize_config.json new file mode 100644 index 0000000..aa6cd69 --- /dev/null +++ b/config/sequelize_config.json @@ -0,0 +1,26 @@ +{ + "development": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_development", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "test": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_test", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "production": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_production", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + } +} diff --git a/connection.js b/connection.js index 331c6b8..5e80c4d 100644 --- a/connection.js +++ b/connection.js @@ -1,6 +1,6 @@ const env = process.env.NODE_ENV || 'development'; const path = require('path') -const config = require(path.join(__dirname, 'config', 'config.json'))[env]; +const config = require(path.join(__dirname, 'config', 'sequelize_config.json'))[env]; const Sequelize = require('sequelize'); const Op = Sequelize.Op; From 4964c8dac742edaec55f3547cf6d6af43eb1996e Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 8 Jan 2019 16:44:38 -0600 Subject: [PATCH 023/125] remove outdated test files --- test/test-data.js | 59 ---------------------------- test/test.js | 97 ----------------------------------------------- 2 files changed, 156 deletions(-) delete mode 100644 test/test-data.js delete mode 100644 test/test.js diff --git a/test/test-data.js b/test/test-data.js deleted file mode 100644 index 1578683..0000000 --- a/test/test-data.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports.people = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - "email": "leonardo.vinci@art.com" - }, - { - "firstName": "Thomas", - "lastName": "Edison", - "email": "thomas.edison@science.com" - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - "email": "vicent.vanGogh@art.com" - }, - { - "firstName": "Albert", - "lastName": "Einstein", - "email": "albert.einstein@science.com" - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - "email": "ludwig.beethoven@art.com" - } -]; - -module.exports.searchPeopleByEmail = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - } -]; - -module.exports.readOneBook = { - "title": "Science of music" -}; - - -module.exports.addDog = { - "name": "toto", - "breed": "chihuahua" -}; - -module.exports.deleteDog = "Item succesfully deleted"; - -module.exports.updateBook = { - "title": "Paintings II", - "genre": "Art" -}; \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index d2c419e..0000000 --- a/test/test.js +++ /dev/null @@ -1,97 +0,0 @@ -var server = require('../server'); -const request = require('supertest'); -const test = require('./test-data'); - -const chai = require('chai'); -const expect = chai.expect; - -describe('Testing Queries Server GraphQL', ()=>{ - it('Read all - people', (done)=>{ - request(server).post('/graphql') - .send({query: '{people{ firstName lastName email}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.people).to.deep.equal(test.people); - done(); - }); - - }); - - it('Search with filter - people by email', (done)=>{ - request(server).post('/graphql') - .send({query: '{ searchPerson(input:{field:email, value:{value:"%art%"}, operator:like}){ firstName lastName}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.searchPerson).to.deep.equal(test.searchPeopleByEmail); - done(); - }); - - }); - - it('Read one by Id - books', (done)=>{ - request(server).post('/graphql') - .send({query: '{readOneBook(id:3){title}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.readOneBook).to.deep.equal(test.readOneBook); - done(); - }); - }); -}); - -describe('Testing Mutations Server GraphQL', ()=>{ - it('Create one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{addDog(name:"toto", breed:"chihuahua"){name breed}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.addDog).to.deep.equal(test.addDog); - done(); - }); - }); - - it('Delete one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{deleteDog(id:6)}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.deleteDog).to.deep.equal(test.deleteDog); - done(); - }); - }); - - it('Update one - books', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{updateBook(id:2, title:"Paintings II"){title genre}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.updateBook).to.deep.equal(test.updateBook); - done(); - }); - }); - - -}); - - - - -server.close(); \ No newline at end of file From 376fd36afe6d87371ca45486686d270ed232a4c1 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 14:05:00 +0100 Subject: [PATCH 024/125] Added scripts to evaluate whether the relational database server is running. Will be used within docker-compose to ensure smooth startup. --- migrateDbAndStartServer.bash | 17 +++++++++++++++++ utils/testSequelizeDbServerAvailable.js | 11 +++++++++++ 2 files changed, 28 insertions(+) create mode 100644 migrateDbAndStartServer.bash create mode 100644 utils/testSequelizeDbServerAvailable.js diff --git a/migrateDbAndStartServer.bash b/migrateDbAndStartServer.bash new file mode 100644 index 0000000..d2625c0 --- /dev/null +++ b/migrateDbAndStartServer.bash @@ -0,0 +1,17 @@ +#!/bin/bash + +# Wait until the relational database-server up and running +waited=0 +until node ./utils/testSequelizeDbServerAvailable.js > /dev/null 2>&1 +do + if [ $waited == 240 ]; then + echo -e '\nERROR: Time out reached while waiting for relational database server to be available.\n' + exit 0 + fi + sleep 2 + waited=$(expr $waited + 2) +done + +# Run the migrations and start GraphQL-server +./node_modules/.bin/sequelize db:migrate && \ + npm start diff --git a/utils/testSequelizeDbServerAvailable.js b/utils/testSequelizeDbServerAvailable.js new file mode 100644 index 0000000..3081a02 --- /dev/null +++ b/utils/testSequelizeDbServerAvailable.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +const path = require('path') + +try { + const Sequelize = require(path.join(__dirname, '..', 'connection.js')) + return 1 +} catch (exception) { + console.log('Could not connect to relational database server.') + return 0 +} From a812734a8f99c3a7a910ac2d8e9e7955394f2316 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 14:22:31 +0100 Subject: [PATCH 025/125] Corrected node script return values to work with BASH. --- utils/testSequelizeDbServerAvailable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/testSequelizeDbServerAvailable.js b/utils/testSequelizeDbServerAvailable.js index 3081a02..dc07a0c 100644 --- a/utils/testSequelizeDbServerAvailable.js +++ b/utils/testSequelizeDbServerAvailable.js @@ -3,9 +3,9 @@ const path = require('path') try { - const Sequelize = require(path.join(__dirname, '..', 'connection.js')) - return 1 + let Sequelize = require(path.join(__dirname, '..', 'connection.js')) + return process.exit(0) } catch (exception) { console.log('Could not connect to relational database server.') - return 0 + return process.exit(1) } From 8da0563013ba6654a61059f305dc0b38b87dd348 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 14:56:35 +0100 Subject: [PATCH 026/125] Using Sequelize's 'authenticate()' to test connection. --- utils/testSequelizeDbServerAvailable.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/utils/testSequelizeDbServerAvailable.js b/utils/testSequelizeDbServerAvailable.js index dc07a0c..6ae5e63 100644 --- a/utils/testSequelizeDbServerAvailable.js +++ b/utils/testSequelizeDbServerAvailable.js @@ -1,11 +1,15 @@ #!/usr/bin/env node const path = require('path') +const Sequelize = require(path.join(__dirname, '..', 'connection.js')) -try { - let Sequelize = require(path.join(__dirname, '..', 'connection.js')) - return process.exit(0) -} catch (exception) { - console.log('Could not connect to relational database server.') - return process.exit(1) +async function checkConnection() { + try { + await Sequelize.authenticate() + return process.exit(0) + } catch (exception) { + return process.exit(1) + } } + +checkConnection() From c149efc53dfbb3ad7df48775904ca445d3e17987 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 17:22:17 +0100 Subject: [PATCH 027/125] Using correct exit values --- migrateDbAndStartServer.bash => migrateDbAndStartServer.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename migrateDbAndStartServer.bash => migrateDbAndStartServer.sh (82%) diff --git a/migrateDbAndStartServer.bash b/migrateDbAndStartServer.sh similarity index 82% rename from migrateDbAndStartServer.bash rename to migrateDbAndStartServer.sh index d2625c0..bf78aba 100644 --- a/migrateDbAndStartServer.bash +++ b/migrateDbAndStartServer.sh @@ -2,11 +2,11 @@ # Wait until the relational database-server up and running waited=0 -until node ./utils/testSequelizeDbServerAvailable.js > /dev/null 2>&1 +until node ./utils/testSequelizeDbServerAvailable.js do if [ $waited == 240 ]; then echo -e '\nERROR: Time out reached while waiting for relational database server to be available.\n' - exit 0 + exit 1 fi sleep 2 waited=$(expr $waited + 2) From 0e05ff87945a43bc88b0b5f4f7696ae13ae45e9b Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 17:39:14 +0100 Subject: [PATCH 028/125] Running migrations and server startup sequentially, exit 1 on fail of migrations. --- migrateDbAndStartServer.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh index bf78aba..a63c43e 100644 --- a/migrateDbAndStartServer.sh +++ b/migrateDbAndStartServer.sh @@ -12,6 +12,10 @@ do waited=$(expr $waited + 2) done -# Run the migrations and start GraphQL-server -./node_modules/.bin/sequelize db:migrate && \ - npm start +# Run the migrations +if ! ./node_modules/.bin/sequelize db:migrate; then + exit 1 +fi + +# Start GraphQL-server +npm start From 9c153f745272adab624ddce2240622bfe2738c8b Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Wed, 16 Jan 2019 17:46:25 +0100 Subject: [PATCH 029/125] Failing migrations echo an ERROR to stdout. --- migrateDbAndStartServer.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh index a63c43e..d0d05bd 100644 --- a/migrateDbAndStartServer.sh +++ b/migrateDbAndStartServer.sh @@ -14,6 +14,7 @@ done # Run the migrations if ! ./node_modules/.bin/sequelize db:migrate; then + echo -e '\nERROR: Migrating the relational database(s) caused an error.\n' exit 1 fi From 612ab7f3eff87e15bdddde384862168c3e7c3f63 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 8 Jan 2019 16:44:38 -0600 Subject: [PATCH 030/125] remove outdated test files --- test/test-data.js | 59 ---------------------------- test/test.js | 97 ----------------------------------------------- 2 files changed, 156 deletions(-) delete mode 100644 test/test-data.js delete mode 100644 test/test.js diff --git a/test/test-data.js b/test/test-data.js deleted file mode 100644 index 1578683..0000000 --- a/test/test-data.js +++ /dev/null @@ -1,59 +0,0 @@ -module.exports.people = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - "email": "leonardo.vinci@art.com" - }, - { - "firstName": "Thomas", - "lastName": "Edison", - "email": "thomas.edison@science.com" - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - "email": "vicent.vanGogh@art.com" - }, - { - "firstName": "Albert", - "lastName": "Einstein", - "email": "albert.einstein@science.com" - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - "email": "ludwig.beethoven@art.com" - } -]; - -module.exports.searchPeopleByEmail = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - } -]; - -module.exports.readOneBook = { - "title": "Science of music" -}; - - -module.exports.addDog = { - "name": "toto", - "breed": "chihuahua" -}; - -module.exports.deleteDog = "Item succesfully deleted"; - -module.exports.updateBook = { - "title": "Paintings II", - "genre": "Art" -}; \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index d2c419e..0000000 --- a/test/test.js +++ /dev/null @@ -1,97 +0,0 @@ -var server = require('../server'); -const request = require('supertest'); -const test = require('./test-data'); - -const chai = require('chai'); -const expect = chai.expect; - -describe('Testing Queries Server GraphQL', ()=>{ - it('Read all - people', (done)=>{ - request(server).post('/graphql') - .send({query: '{people{ firstName lastName email}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.people).to.deep.equal(test.people); - done(); - }); - - }); - - it('Search with filter - people by email', (done)=>{ - request(server).post('/graphql') - .send({query: '{ searchPerson(input:{field:email, value:{value:"%art%"}, operator:like}){ firstName lastName}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.searchPerson).to.deep.equal(test.searchPeopleByEmail); - done(); - }); - - }); - - it('Read one by Id - books', (done)=>{ - request(server).post('/graphql') - .send({query: '{readOneBook(id:3){title}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.readOneBook).to.deep.equal(test.readOneBook); - done(); - }); - }); -}); - -describe('Testing Mutations Server GraphQL', ()=>{ - it('Create one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{addDog(name:"toto", breed:"chihuahua"){name breed}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.addDog).to.deep.equal(test.addDog); - done(); - }); - }); - - it('Delete one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{deleteDog(id:6)}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.deleteDog).to.deep.equal(test.deleteDog); - done(); - }); - }); - - it('Update one - books', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{updateBook(id:2, title:"Paintings II"){title genre}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.updateBook).to.deep.equal(test.updateBook); - done(); - }); - }); - - -}); - - - - -server.close(); \ No newline at end of file From d00851c9b4c8658131d84dc1a485b10694891e0f Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 16 Jan 2019 16:17:10 -0600 Subject: [PATCH 031/125] change name config file --- config/{sequelize_config.json => config.json} | 0 connection.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename config/{sequelize_config.json => config.json} (100%) diff --git a/config/sequelize_config.json b/config/config.json similarity index 100% rename from config/sequelize_config.json rename to config/config.json diff --git a/connection.js b/connection.js index 5e80c4d..331c6b8 100644 --- a/connection.js +++ b/connection.js @@ -1,6 +1,6 @@ const env = process.env.NODE_ENV || 'development'; const path = require('path') -const config = require(path.join(__dirname, 'config', 'sequelize_config.json'))[env]; +const config = require(path.join(__dirname, 'config', 'config.json'))[env]; const Sequelize = require('sequelize'); const Op = Sequelize.Op; From aa041d0ab6529ffdbc29a5d8d5bf4cfd816e5b27 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 16 Jan 2019 19:03:23 -0600 Subject: [PATCH 032/125] add user and role data models --- .gitignore | 7 +- config/config.json | 2 +- migrations/20190117003021-role.js | 54 ++++ migrations/20190117003021-user.js | 54 ++++ .../20190117003021-z-through-role_to_user.js | 61 ++++ models/index.js | 27 ++ models/role.js | 32 ++ models/user.js | 32 ++ resolvers/index.js | 16 + resolvers/role.js | 300 ++++++++++++++++++ resolvers/user.js | 300 ++++++++++++++++++ schemas/commons.js | 27 ++ schemas/role.js | 53 ++++ schemas/user.js | 53 ++++ 14 files changed, 1011 insertions(+), 7 deletions(-) create mode 100644 migrations/20190117003021-role.js create mode 100644 migrations/20190117003021-user.js create mode 100644 migrations/20190117003021-z-through-role_to_user.js create mode 100644 models/index.js create mode 100644 models/role.js create mode 100644 models/user.js create mode 100644 resolvers/index.js create mode 100644 resolvers/role.js create mode 100644 resolvers/user.js create mode 100644 schemas/commons.js create mode 100644 schemas/role.js create mode 100644 schemas/user.js diff --git a/.gitignore b/.gitignore index 3900f98..195cbfd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,4 @@ node_modules npm-debug.log package-lock.json -# ModelGen output folders -migrations -models -models-webservice -resolvers -schemas \ No newline at end of file + diff --git a/config/config.json b/config/config.json index aa6cd69..e33aaa7 100644 --- a/config/config.json +++ b/config/config.json @@ -3,7 +3,7 @@ "username": "sciencedb", "password": "sciencedb", "database": "sciencedb_development", - "host": "postgres", + "host": "127.0.0.1", "dialect": "postgres", "operatorsAliases": false }, diff --git a/migrations/20190117003021-role.js b/migrations/20190117003021-role.js new file mode 100644 index 0000000..1e0a2d5 --- /dev/null +++ b/migrations/20190117003021-role.js @@ -0,0 +1,54 @@ +'use strict'; + +/** + * @module - Migrations to create and to undo a table correpondant to a sequelize model. + */ +module.exports = { + + /** + * up - Creates a table with the fields specified in the the createTable function. + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was created successfully, rejected otherwise. + */ + up: function(queryInterface, Sequelize) { + return queryInterface.createTable('roles', { + + id: { + type: Sequelize.INTEGER, + autoIncrement: true, + primaryKey: true + }, + + createdAt: { + type: Sequelize.DATE + }, + + updatedAt: { + type: Sequelize.DATE + }, + + name: { + type: Sequelize.STRING, + unique: true + }, + description: { + type: Sequelize.STRING + } + + }); + }, + + /** + * down - Deletes a table. + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was deleted successfully, rejected otherwise. + */ + down: function(queryInterface, Sequelize) { + return queryInterface.dropTable('roles'); + } + +}; diff --git a/migrations/20190117003021-user.js b/migrations/20190117003021-user.js new file mode 100644 index 0000000..701e36d --- /dev/null +++ b/migrations/20190117003021-user.js @@ -0,0 +1,54 @@ +'use strict'; + +/** + * @module - Migrations to create and to undo a table correpondant to a sequelize model. + */ +module.exports = { + + /** + * up - Creates a table with the fields specified in the the createTable function. + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was created successfully, rejected otherwise. + */ + up: function(queryInterface, Sequelize) { + return queryInterface.createTable('users', { + + id: { + type: Sequelize.INTEGER, + autoIncrement: true, + primaryKey: true + }, + + createdAt: { + type: Sequelize.DATE + }, + + updatedAt: { + type: Sequelize.DATE + }, + + email: { + type: Sequelize.STRING, + unique: true + }, + password: { + type: Sequelize.STRING + } + + }); + }, + + /** + * down - Deletes a table. + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was deleted successfully, rejected otherwise. + */ + down: function(queryInterface, Sequelize) { + return queryInterface.dropTable('users'); + } + +}; diff --git a/migrations/20190117003021-z-through-role_to_user.js b/migrations/20190117003021-z-through-role_to_user.js new file mode 100644 index 0000000..0235f45 --- /dev/null +++ b/migrations/20190117003021-z-through-role_to_user.js @@ -0,0 +1,61 @@ +'use strict'; + +/** + * @module - Migrations to creates a through table correspondant to manay-to-many association between two sequelize models. + */ +module.exports = { + + /** + * up - Creates a table in the database for storing a many-to-many association + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was succesfully created. + */ + up: function(queryInterface, Sequelize) { + return queryInterface.createTable('role_to_user', { + + createdAt: { + type: Sequelize.DATE + }, + + updatedAt: { + type: Sequelize.DATE + }, + + userId: { + type: Sequelize.INTEGER, + onDelete: 'CASCADE', + references: { + model: 'users', + key: 'id' + } + }, + + roleId: { + type: Sequelize.INTEGER, + onDelete: 'CASCADE', + references: { + model: 'roles', + key: 'id' + } + } + }).then(() => { + return queryInterface.addIndex('role_to_user', ['userId']); + }).then(() => { + return queryInterface.addIndex('role_to_user', ['roleId']); + }); + }, + + /** + * down - Deletes a table in the database for storing a many-to-many association + * + * @param {object} queryInterface Used to modify the table in the database. + * @param {object} Sequelize Sequelize instance with data types included + * @return {promise} Resolved if the table was succesfully deleted. + */ + down: function(queryInterface, Sequelize) { + return queryInterface.dropTable('role_to_user'); + } + +}; \ No newline at end of file diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..b5d4532 --- /dev/null +++ b/models/index.js @@ -0,0 +1,27 @@ + + const fs = require('fs'); + const path = require('path') + sequelize = require('../connection'); + + var models = {}; + + //grabs all the models in your models folder, adds them to the models object + fs.readdirSync(__dirname) + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + var model = sequelize['import'](path.join(__dirname, file)); + models[model.name] = model; + }); + //Important: creates associations based on associations defined in associate function in the model files + Object.keys(models).forEach(function(modelName) { + if (models[modelName].associate) { + models[modelName].associate(models); + } + }); + //update tables with association (temporary, just for testing purposes) + //this part is suppose to be done in the migration file + //sequelize.sync({force: true}); + module.exports = models; + \ No newline at end of file diff --git a/models/role.js b/models/role.js new file mode 100644 index 0000000..57ae576 --- /dev/null +++ b/models/role.js @@ -0,0 +1,32 @@ +'use strict'; + +const Sequelize = require('sequelize'); + +/** + * module - Creates a sequelize model + * + * @param {object} sequelize Sequelize instance. + * @param {object} DataTypes Allowed sequelize data types. + * @return {object} Sequelize model with associations defined + */ +module.exports = function(sequelize, DataTypes) { + var Role = sequelize.define('role', { + + name: { + type: Sequelize.STRING, + unique: true + }, + description: { + type: Sequelize.STRING + } + }); + + Role.associate = function(models) { + Role.belongsToMany(models.user, { + through: 'role_to_user', + onDelete: 'CASCADE' + }); + }; + + return Role; +}; diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..c740cae --- /dev/null +++ b/models/user.js @@ -0,0 +1,32 @@ +'use strict'; + +const Sequelize = require('sequelize'); + +/** + * module - Creates a sequelize model + * + * @param {object} sequelize Sequelize instance. + * @param {object} DataTypes Allowed sequelize data types. + * @return {object} Sequelize model with associations defined + */ +module.exports = function(sequelize, DataTypes) { + var User = sequelize.define('user', { + + email: { + type: Sequelize.STRING, + unique: true + }, + password: { + type: Sequelize.STRING + } + }); + + User.associate = function(models) { + User.belongsToMany(models.role, { + through: 'role_to_user', + onDelete: 'CASCADE' + }); + }; + + return User; +}; diff --git a/resolvers/index.js b/resolvers/index.js new file mode 100644 index 0000000..9e43574 --- /dev/null +++ b/resolvers/index.js @@ -0,0 +1,16 @@ +const fs = require('fs'); + +let resolvers = {}; + +module.exports = resolvers; + +fs.readdirSync(__dirname) + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + const resolversModel = require('./' + file); + for (resolver in resolversModel) { + resolvers[resolver] = resolversModel[resolver]; + } + }); \ No newline at end of file diff --git a/resolvers/role.js b/resolvers/role.js new file mode 100644 index 0000000..6c55504 --- /dev/null +++ b/resolvers/role.js @@ -0,0 +1,300 @@ +/* + Resolvers for basic CRUD operations +*/ + +const role = require('../models/index').role; +const searchArg = require('../utils/search-argument'); +const fileTools = require('../utils/file-tools'); +const helper = require('../utils/helper'); +const globals = require('../config/globals'); +const checkAuthorization = require('../utils/check-authorization'); +const path = require('path') +const fs = require('fs') +const uuidv4 = require('uuidv4') + + + +/** + * role.prototype.usersFilter - Check user authorization and return certain number, specified in pagination argument, of records + * associated with the current instance, this records should also + * holds the condition of search argument, all of them sorted as specified by the order argument. + * + * @param {object} search Search argument for filtering associated records + * @param {array} order Type of sorting (ASC, DESC) for each field + * @param {object} pagination Offset and limit to get the records from and to respectively + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {array} Array of associated records holding conditions specified by search, order and pagination argument + */ +role.prototype.usersFilter = function({ + search, + order, + pagination +}, context) { + + let options = {}; + + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return this.countUsers(options).then(items => { + if (order !== undefined) { + options['order'] = order.map((orderItem) => { + return [orderItem.field, orderItem.order]; + }); + } + + if (pagination !== undefined) { + options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; + options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; + } else { + options['offset'] = 0; + options['limit'] = items; + } + + if (globals.LIMIT_RECORDS < options['limit']) { + throw new Error(`Request of total usersFilter exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); + } + return this.getUsers(options); + }).catch(error => { + console.log("Catched the error in usersFilter ", error); + return error; + }); +} + +/** + * role.prototype.countFilteredUsers - Count number of associated records that holds the conditions specified in the search argument + * + * @param {object} {search} description + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {type} Number of associated records that holds the conditions specified in the search argument + */ +role.prototype.countFilteredUsers = function({ + search +}, context) { + + let options = {}; + + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return this.countUsers(options); +} + + + + +module.exports = { + + /** + * roles - Check user authorization and return certain number, specified in pagination argument, of records that + * holds the condition of search argument, all of them sorted as specified by the order argument. + * + * @param {object} search Search argument for filtering records + * @param {array} order Type of sorting (ASC, DESC) for each field + * @param {object} pagination Offset and limit to get the records from and to respectively + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {array} Array of records holding conditions specified by search, order and pagination argument + */ + roles: function({ + search, + order, + pagination + }, context) { + if (checkAuthorization(context, 'roles', 'read') == true) { + let options = {}; + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return role.count(options).then(items => { + if (order !== undefined) { + options['order'] = order.map((orderItem) => { + return [orderItem.field, orderItem.order]; + }); + } + + if (pagination !== undefined) { + options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; + options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; + } else { + options['offset'] = 0; + options['limit'] = items; + } + + if (globals.LIMIT_RECORDS < options['limit']) { + throw new Error(`Request of total roles exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); + } + return role.findAll(options); + }).catch(error => { + console.log("Catched the error in roles ", error); + return error; + }); + } else { + return new Error("You don't have authorization to perform this action"); + } + }, + + /** + * readOneRole - Check user authorization and return one book with the specified id in the id argument. + * + * @param {number} {id} Id of the record to retrieve + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Record with id requested + */ + readOneRole: function({ + id + }, context) { + if (checkAuthorization(context, 'roles', 'read') == true) { + return role.findOne({ + where: { + id: id + } + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * addRole - Check user authorization and creates a new record with data specified in the input argument + * + * @param {object} input Info of each field to create the new record + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} New record created + */ + addRole: function(input, context) { + if (checkAuthorization(context, 'roles', 'create') == true) { + return role.create(input) + .then(role => { + if (input.addUsers) { + role.setUsers(input.addUsers); + } + return role; + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * bulkAddRoleXlsx - Load xlsx file of records + * + * @param {string} _ First parameter is not used + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + */ + bulkAddRoleXlsx: function(_, context) { + let xlsxObjs = fileTools.parseXlsx(context.request.files.xlsx_file.data.toString('binary')); + return role.bulkCreate(xlsxObjs, { + validate: true + }); + }, + + /** + * bulkAddRoleCsv - Load csv file of records + * + * @param {string} _ First parameter is not used + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + */ + bulkAddRoleCsv: function(_, context) { + delim = context.request.body.delim; + cols = context.request.body.cols; + tmpFile = path.join(__dirname, uuidv4() + '.csv') + return context.request.files.csv_file.mv(tmpFile).then(() => { + return fileTools.parseCsvStream(tmpFile, role, delim, cols) + }).catch((err) => { + return new Error(err); + }).then(() => { + fs.unlinkSync(tmpFile) + }) + }, + + /** + * deleteRole - Check user authorization and delete a record with the specified id in the id argument. + * + * @param {number} {id} Id of the record to delete + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {string} Message indicating if deletion was successfull. + */ + deleteRole: function({ + id + }, context) { + if (checkAuthorization(context, 'roles', 'delete') == true) { + return role.findById(id) + .then(role => { + return role.destroy() + .then(() => { + return 'Item succesfully deleted'; + }); + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * updateRole - Check user authorization and update the record specified in the input argument + * + * @param {object} input record to update and new info to update + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Updated record + */ + updateRole: function(input, context) { + if (checkAuthorization(context, 'roles', 'update') == true) { + return role.findById(input.id) + .then(role => { + if (input.addUsers) { + role.addUsers(input.addUsers); + } + if (input.removeUsers) { + role.removeUsers(input.removeUsers); + } + return role.update(input); + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * countRoles - Count number of records that holds the conditions specified in the search argument + * + * @param {object} {search} Search argument for filtering records + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {number} Number of records that holds the conditions specified in the search argument + */ + countRoles: function({ + search + }, context) { + let options = {}; + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return role.count(options); + }, + + /** + * vueTableRole - Return table of records as needed for displaying a vuejs table + * + * @param {string} _ First parameter is not used + * @param {type} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Records with format as needed for displaying a vuejs table + */ + vueTableRole: function(_, context) { + if (checkAuthorization(context, 'roles', 'read') == true) { + return helper.vueTable(context.request, role, ["id", "name", "description"]); + } else { + return "You don't have authorization to perform this action"; + } + } +} \ No newline at end of file diff --git a/resolvers/user.js b/resolvers/user.js new file mode 100644 index 0000000..821020f --- /dev/null +++ b/resolvers/user.js @@ -0,0 +1,300 @@ +/* + Resolvers for basic CRUD operations +*/ + +const user = require('../models/index').user; +const searchArg = require('../utils/search-argument'); +const fileTools = require('../utils/file-tools'); +const helper = require('../utils/helper'); +const globals = require('../config/globals'); +const checkAuthorization = require('../utils/check-authorization'); +const path = require('path') +const fs = require('fs') +const uuidv4 = require('uuidv4') + + + +/** + * user.prototype.rolesFilter - Check user authorization and return certain number, specified in pagination argument, of records + * associated with the current instance, this records should also + * holds the condition of search argument, all of them sorted as specified by the order argument. + * + * @param {object} search Search argument for filtering associated records + * @param {array} order Type of sorting (ASC, DESC) for each field + * @param {object} pagination Offset and limit to get the records from and to respectively + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {array} Array of associated records holding conditions specified by search, order and pagination argument + */ +user.prototype.rolesFilter = function({ + search, + order, + pagination +}, context) { + + let options = {}; + + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return this.countRoles(options).then(items => { + if (order !== undefined) { + options['order'] = order.map((orderItem) => { + return [orderItem.field, orderItem.order]; + }); + } + + if (pagination !== undefined) { + options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; + options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; + } else { + options['offset'] = 0; + options['limit'] = items; + } + + if (globals.LIMIT_RECORDS < options['limit']) { + throw new Error(`Request of total rolesFilter exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); + } + return this.getRoles(options); + }).catch(error => { + console.log("Catched the error in rolesFilter ", error); + return error; + }); +} + +/** + * user.prototype.countFilteredRoles - Count number of associated records that holds the conditions specified in the search argument + * + * @param {object} {search} description + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {type} Number of associated records that holds the conditions specified in the search argument + */ +user.prototype.countFilteredRoles = function({ + search +}, context) { + + let options = {}; + + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return this.countRoles(options); +} + + + + +module.exports = { + + /** + * users - Check user authorization and return certain number, specified in pagination argument, of records that + * holds the condition of search argument, all of them sorted as specified by the order argument. + * + * @param {object} search Search argument for filtering records + * @param {array} order Type of sorting (ASC, DESC) for each field + * @param {object} pagination Offset and limit to get the records from and to respectively + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {array} Array of records holding conditions specified by search, order and pagination argument + */ + users: function({ + search, + order, + pagination + }, context) { + if (checkAuthorization(context, 'users', 'read') == true) { + let options = {}; + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return user.count(options).then(items => { + if (order !== undefined) { + options['order'] = order.map((orderItem) => { + return [orderItem.field, orderItem.order]; + }); + } + + if (pagination !== undefined) { + options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; + options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; + } else { + options['offset'] = 0; + options['limit'] = items; + } + + if (globals.LIMIT_RECORDS < options['limit']) { + throw new Error(`Request of total users exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); + } + return user.findAll(options); + }).catch(error => { + console.log("Catched the error in users ", error); + return error; + }); + } else { + return new Error("You don't have authorization to perform this action"); + } + }, + + /** + * readOneUser - Check user authorization and return one book with the specified id in the id argument. + * + * @param {number} {id} Id of the record to retrieve + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Record with id requested + */ + readOneUser: function({ + id + }, context) { + if (checkAuthorization(context, 'users', 'read') == true) { + return user.findOne({ + where: { + id: id + } + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * addUser - Check user authorization and creates a new record with data specified in the input argument + * + * @param {object} input Info of each field to create the new record + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} New record created + */ + addUser: function(input, context) { + if (checkAuthorization(context, 'users', 'create') == true) { + return user.create(input) + .then(user => { + if (input.addRoles) { + user.setRoles(input.addRoles); + } + return user; + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * bulkAddUserXlsx - Load xlsx file of records + * + * @param {string} _ First parameter is not used + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + */ + bulkAddUserXlsx: function(_, context) { + let xlsxObjs = fileTools.parseXlsx(context.request.files.xlsx_file.data.toString('binary')); + return user.bulkCreate(xlsxObjs, { + validate: true + }); + }, + + /** + * bulkAddUserCsv - Load csv file of records + * + * @param {string} _ First parameter is not used + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + */ + bulkAddUserCsv: function(_, context) { + delim = context.request.body.delim; + cols = context.request.body.cols; + tmpFile = path.join(__dirname, uuidv4() + '.csv') + return context.request.files.csv_file.mv(tmpFile).then(() => { + return fileTools.parseCsvStream(tmpFile, user, delim, cols) + }).catch((err) => { + return new Error(err); + }).then(() => { + fs.unlinkSync(tmpFile) + }) + }, + + /** + * deleteUser - Check user authorization and delete a record with the specified id in the id argument. + * + * @param {number} {id} Id of the record to delete + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {string} Message indicating if deletion was successfull. + */ + deleteUser: function({ + id + }, context) { + if (checkAuthorization(context, 'users', 'delete') == true) { + return user.findById(id) + .then(user => { + return user.destroy() + .then(() => { + return 'Item succesfully deleted'; + }); + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * updateUser - Check user authorization and update the record specified in the input argument + * + * @param {object} input record to update and new info to update + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Updated record + */ + updateUser: function(input, context) { + if (checkAuthorization(context, 'users', 'update') == true) { + return user.findById(input.id) + .then(user => { + if (input.addRoles) { + user.addRoles(input.addRoles); + } + if (input.removeRoles) { + user.removeRoles(input.removeRoles); + } + return user.update(input); + }); + } else { + return "You don't have authorization to perform this action"; + } + }, + + /** + * countUsers - Count number of records that holds the conditions specified in the search argument + * + * @param {object} {search} Search argument for filtering records + * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {number} Number of records that holds the conditions specified in the search argument + */ + countUsers: function({ + search + }, context) { + let options = {}; + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } + + return user.count(options); + }, + + /** + * vueTableUser - Return table of records as needed for displaying a vuejs table + * + * @param {string} _ First parameter is not used + * @param {type} context Provided to every resolver holds contextual information like the resquest query and user info. + * @return {object} Records with format as needed for displaying a vuejs table + */ + vueTableUser: function(_, context) { + if (checkAuthorization(context, 'users', 'read') == true) { + return helper.vueTable(context.request, user, ["id", "email", "password"]); + } else { + return "You don't have authorization to perform this action"; + } + } +} \ No newline at end of file diff --git a/schemas/commons.js b/schemas/commons.js new file mode 100644 index 0000000..4cacc60 --- /dev/null +++ b/schemas/commons.js @@ -0,0 +1,27 @@ +module.exports = ` + + enum Operator{ + like + or + and + eq + between + in + } + + enum Order{ + DESC + ASC + } + + input typeValue{ + type: String + value: String! + } + + input paginationInput{ + limit: Int + offset: Int + } + +`; \ No newline at end of file diff --git a/schemas/role.js b/schemas/role.js new file mode 100644 index 0000000..7c9fc00 --- /dev/null +++ b/schemas/role.js @@ -0,0 +1,53 @@ +module.exports = ` + type Role { + id: ID + name: String + description: String + usersFilter(search: searchUserInput, order: [ orderUserInput ], pagination: paginationInput): [User] + countFilteredUsers(search: searchUserInput) : Int + } + + type VueTableRole{ + data : [Role] + total: Int + per_page: Int + current_page: Int + last_page: Int + prev_page_url: String + next_page_url: String + from: Int + to: Int + } + + enum RoleField { + id + name + description + } + + input searchRoleInput { + field: RoleField + value: typeValue + operator: Operator + search: [searchRoleInput] + } + + input orderRoleInput{ + field: RoleField + order: Order + } + + type Query { + roles(search: searchRoleInput, order: [ orderRoleInput ], pagination: paginationInput ): [Role] + readOneRole(id: ID!): Role + countRoles(search: searchRoleInput ): Int + vueTableRole : VueTableRole } + + type Mutation { + addRole( name: String, description: String , addUsers:[ID] ): Role + deleteRole(id: ID!): String! + updateRole(id: ID!, name: String, description: String , addUsers:[ID], removeUsers:[ID] ): Role! + bulkAddRoleXlsx: [Role] + bulkAddRoleCsv: [Role] +} + `; \ No newline at end of file diff --git a/schemas/user.js b/schemas/user.js new file mode 100644 index 0000000..44d094b --- /dev/null +++ b/schemas/user.js @@ -0,0 +1,53 @@ +module.exports = ` + type User { + id: ID + email: String + password: String + rolesFilter(search: searchRoleInput, order: [ orderRoleInput ], pagination: paginationInput): [Role] + countFilteredRoles(search: searchRoleInput) : Int + } + + type VueTableUser{ + data : [User] + total: Int + per_page: Int + current_page: Int + last_page: Int + prev_page_url: String + next_page_url: String + from: Int + to: Int + } + + enum UserField { + id + email + password + } + + input searchUserInput { + field: UserField + value: typeValue + operator: Operator + search: [searchUserInput] + } + + input orderUserInput{ + field: UserField + order: Order + } + + type Query { + users(search: searchUserInput, order: [ orderUserInput ], pagination: paginationInput ): [User] + readOneUser(id: ID!): User + countUsers(search: searchUserInput ): Int + vueTableUser : VueTableUser } + + type Mutation { + addUser( email: String, password: String , addRoles:[ID] ): User + deleteUser(id: ID!): String! + updateUser(id: ID!, email: String, password: String , addRoles:[ID], removeRoles:[ID] ): User! + bulkAddUserXlsx: [User] + bulkAddUserCsv: [User] +} + `; \ No newline at end of file From acf11ba386ff9d079fd25d2f503242a47dc52bde Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 18 Jan 2019 01:11:30 -0600 Subject: [PATCH 033/125] login functionality - test check authorization --- acl_rules.js | 4 +- package-lock.json | 435 +++++++++++++++++++++++++++++++++++ package.json | 5 +- resolvers/login.js | 39 ++++ resolvers/user.js | 69 +++--- schemas/login.js | 3 + utils/check-authorization.js | 22 +- 7 files changed, 528 insertions(+), 49 deletions(-) create mode 100644 resolvers/login.js create mode 100644 schemas/login.js diff --git a/acl_rules.js b/acl_rules.js index ad76120..ce28b79 100644 --- a/acl_rules.js +++ b/acl_rules.js @@ -1,8 +1,8 @@ module.exports = { aclRules: [{ - roles: 'administrator', + roles: 'admin', allows: [{ - resources: '*', + resources: ['users', 'roles'], permissions: '*' }] }, diff --git a/package-lock.json b/package-lock.json index dbbbd3f..29a24fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,6 +101,436 @@ "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" }, + "bcrypt": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.3.tgz", + "integrity": "sha512-4EuzUo6K790QC3uq/ogzy9w2Hc7XDIBoEndU5y7l7YaEAwQF8vyFqv6tC30+gOBZvyxk3F632xzKBQoLNz2pjg==", + "requires": { + "nan": "2.12.1", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.5" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "2.3.4" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.4", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.1.1", + "bundled": true, + "requires": { + "minipass": "2.3.4" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.24", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.4", + "nopt": "4.0.1", + "npm-packlist": "1.1.12", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.6.2", + "semver": "5.6.0", + "tar": "4.4.8" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true + }, + "npm-packlist": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.5" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "readable-stream": { + "version": "2.3.5", + "bundled": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.6.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "requires": { + "chownr": "1.1.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.4", + "minizlib": "1.1.1", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + } + } + }, "bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", @@ -1437,6 +1867,11 @@ } } }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", diff --git a/package.json b/package.json index ab293d6..87c7172 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "acl": "^0.4.11", "awaitify-stream": "^1.0.2", + "bcrypt": "^3.0.3", "bcryptjs": "^2.4.3", "chai": "^4.1.2", "cors": "^2.8.4", @@ -25,14 +26,14 @@ "lodash": "^4.17.5", "mathjs": "^5.2.0", "merge-graphql-schemas": "^1.5.1", + "mysql2": "^1.6.4", "pg": "^7.4.1", "pg-hstore": "^2.3.2", "sequelize": "^4.35.2", "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", "uuidv4": "^2.0.0", - "xlsx": "^0.12.11", - "mysql2": "^1.6.4" + "xlsx": "^0.12.11" }, "devDependencies": { "mocha": "^5.2.0" diff --git a/resolvers/login.js b/resolvers/login.js new file mode 100644 index 0000000..88db118 --- /dev/null +++ b/resolvers/login.js @@ -0,0 +1,39 @@ +const bcrypt = require('bcrypt') +const jsonwebtoken = require('jsonwebtoken') +const user = require('../models/index').user + +module.exports = { + + + /** + * login - Search for email in users table and returns a webtoken if the password is valid. + * + * @param {String} email User's email + * @param {String} password User's password + * @return {String} Webtoken with user's data encoded + */ + login: async function({ email, password }) { + + const user_data = await user.findOne({ where: { email } }) + + if (!user_data) { + throw new Error('No user with that email') + } + + const valid = (password==user_data.password); //await bcrypt.compare(password, user_data.password) + + if (!valid) { + throw new Error('Incorrect password') + } + const roles = await user_data.getRoles(); + const name_roles = roles.map( x =>{ return x.name }) + console.log("ROLES: ", name_roles); + // return json web token + return jsonwebtoken.sign({ + id: user_data.id, + email: user_data.email, + roles: name_roles + }, 'something-secret', { expiresIn: '1h' }) + } + +} diff --git a/resolvers/user.js b/resolvers/user.js index 821020f..fba4d92 100644 --- a/resolvers/user.js +++ b/resolvers/user.js @@ -106,40 +106,45 @@ module.exports = { order, pagination }, context) { - if (checkAuthorization(context, 'users', 'read') == true) { - let options = {}; - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - return user.count(options).then(items => { - if (order !== undefined) { - options['order'] = order.map((orderItem) => { - return [orderItem.field, orderItem.order]; - }); - } + return checkAuthorization(context, 'users', 'read').then( authorization =>{ + if (authorization === true) { + let options = {}; + if (search !== undefined) { + let arg = new searchArg(search); + let arg_sequelize = arg.toSequelize(); + options['where'] = arg_sequelize; + } - if (pagination !== undefined) { - options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; - options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; - } else { - options['offset'] = 0; - options['limit'] = items; - } + return user.count(options).then(items => { + if (order !== undefined) { + options['order'] = order.map((orderItem) => { + return [orderItem.field, orderItem.order]; + }); + } - if (globals.LIMIT_RECORDS < options['limit']) { - throw new Error(`Request of total users exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); - } - return user.findAll(options); - }).catch(error => { - console.log("Catched the error in users ", error); - return error; - }); - } else { - return new Error("You don't have authorization to perform this action"); - } + if (pagination !== undefined) { + options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; + options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; + } else { + options['offset'] = 0; + options['limit'] = items; + } + + if (globals.LIMIT_RECORDS < options['limit']) { + throw new Error(`Request of total users exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); + } + return user.findAll(options); + }).catch(error => { + console.log("Catched the error in users ", error); + return error; + }); + } else { + return new Error("You don't have authorization to perform this action"); + } + }).catch( error =>{ + return error; + }) }, /** @@ -297,4 +302,4 @@ module.exports = { return "You don't have authorization to perform this action"; } } -} \ No newline at end of file +} diff --git a/schemas/login.js b/schemas/login.js new file mode 100644 index 0000000..2b63a09 --- /dev/null +++ b/schemas/login.js @@ -0,0 +1,3 @@ +module.exports = `type Query { + login(password: String, email: String): String! +}` diff --git a/utils/check-authorization.js b/utils/check-authorization.js index 1da8334..1f7f5cd 100644 --- a/utils/check-authorization.js +++ b/utils/check-authorization.js @@ -10,29 +10,25 @@ const secret = 'something-secret'; * @param {object} context context object contains the request info and the acl rules. * @param {string} resource resource to which the user wants to perform an action (i.e. a model). * @param {string} permission action that the user wants to perform to resourse (i.e. read, edit, create). - * @return {boolean} Return true if within the context the user is allowed to perform 'permission' action to the 'resource' model. + * @return {promise} it will resolve true if within the context the user is allowed to perform 'permission' action to the 'resource' model. */ -module.exports = function( context, resource, permission ) { +module.exports = function( context, resource, permission ) { //if there's not authorization rules set if (context.acl == null) return true; let token = context.request.headers["authorization"]; + console.log("TOKEN",token); try{ //Identify user from context let decoded = jwt.verify(token, secret); - //check for permissions of that specific user - let allowed = context.acl.isAllowed(decoded.id, resource, permission); - console.log(typeof decoded.id, ' * ' ,decoded.id); - if(allowed){ - return true; - }else{ - //no permission for user - console.log("Permission dennied..."); - return false; - } + + //check for permissions from specific roles + return context.acl.areAnyRolesAllowed(decoded.roles, resource, permission); }catch(err){ //invalid token console.log("invalid token..."); - return false; + console.log(err); + throw new Error(err); + //return false; } } From 8658019ad625855d4827e93acd3558bfd910663e Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 18 Jan 2019 03:19:19 -0600 Subject: [PATCH 034/125] login route added --- package-lock.json | 28 ++++++++++++++++++++++++++++ package.json | 1 + schemas/login.js | 3 --- server.js | 15 +++++++++++++++ utils/check-authorization.js | 5 +++-- {resolvers => utils}/login.js | 2 +- 6 files changed, 48 insertions(+), 6 deletions(-) delete mode 100644 schemas/login.js rename {resolvers => utils}/login.js (97%) diff --git a/package-lock.json b/package-lock.json index 29a24fb..316b1c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1176,6 +1176,29 @@ "raw-body": "2.3.2" } }, + "express-jwt": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", + "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "requires": { + "async": "1.5.2", + "express-unless": "0.3.1", + "jsonwebtoken": "8.2.0", + "lodash.set": "4.3.2" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + } + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -1613,6 +1636,11 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", diff --git a/package.json b/package.json index 87c7172..2f94b78 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "^4.16.2", "express-fileupload": "^0.4.0", "express-graphql": "^0.6.12", + "express-jwt": "^5.3.1", "faker": "^4.1.0", "graphql": "^0.13.1", "jsonwebtoken": "^8.2.0", diff --git a/schemas/login.js b/schemas/login.js deleted file mode 100644 index 2b63a09..0000000 --- a/schemas/login.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = `type Query { - login(password: String, email: String): String! -}` diff --git a/server.js b/server.js index d49e7de..35aa857 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,11 @@ var express = require('express'); var path = require('path'); var graphqlHTTP = require('express-graphql'); + var jwt = require('express-jwt'); const fileUpload = require('express-fileupload'); + const auth = require('./utils/login'); + const bodyParser = require('body-parser'); + var { buildSchema } = require('graphql'); @@ -58,6 +62,17 @@ console.log(merged_schema) // next(); //}); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); +app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); +app.use('/login', cors(), (req, res)=>{ + + auth.login(req.body).then( (token) =>{ + res.json(token); + }); + +}) + app.use(fileUpload()); /*request is passed as context by default */ app.use('/graphql', cors(), graphqlHTTP((req) => ({ diff --git a/utils/check-authorization.js b/utils/check-authorization.js index 1f7f5cd..44441d2 100644 --- a/utils/check-authorization.js +++ b/utils/check-authorization.js @@ -16,8 +16,9 @@ module.exports = function( context, resource, permission ) { //if there's not authorization rules set if (context.acl == null) return true; - let token = context.request.headers["authorization"]; - console.log("TOKEN",token); + let token_bearer = context.request.headers["authorization"]; + let token = token_bearer.replace("Bearer ",""); + console.log("TOKEN",typeof token, token); try{ //Identify user from context let decoded = jwt.verify(token, secret); diff --git a/resolvers/login.js b/utils/login.js similarity index 97% rename from resolvers/login.js rename to utils/login.js index 88db118..4437e3e 100644 --- a/resolvers/login.js +++ b/utils/login.js @@ -15,7 +15,7 @@ module.exports = { login: async function({ email, password }) { const user_data = await user.findOne({ where: { email } }) - + console.log(user_data); if (!user_data) { throw new Error('No user with that email') } From 8e5f610ad3a934530736ac49f935db8f100c19af Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 18 Jan 2019 18:10:28 -0600 Subject: [PATCH 035/125] middleware as option with acl rules --- server.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index 35aa857..c3f6d86 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,11 @@ var cors = require('cors'); + + /* Server */ + const APP_PORT = 3000; + const app = express(); + /* Temporary solution: acl rules set */ if (process.argv.length > 2 && process.argv[2] == 'acl') { var node_acl = require('acl'); @@ -26,9 +31,7 @@ acl.allow(aclRules); console.log("Authoization rules set!"); - /*For testing purposes*/ - acl.addUserRoles(1, 'guest'); - acl.addUserRoles(2, 'administrator'); + app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); } else { console.log("Open server, no authorization rules"); } @@ -42,9 +45,7 @@ console.log(merged_schema) /* Resolvers*/ var resolvers = require('./resolvers/index'); - /* Server */ - const APP_PORT = 3000; - const app = express(); + //app.use((req, res, next)=> { @@ -64,7 +65,7 @@ console.log(merged_schema) app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); -app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); + app.use('/login', cors(), (req, res)=>{ auth.login(req.body).then( (token) =>{ From f3c2611b25d5dd2a9fb1a3b6b5aa2d30ec2dc326 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 24 Jan 2019 01:50:06 -0600 Subject: [PATCH 036/125] json response token and error catching --- server.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index c3f6d86..90eb79a 100644 --- a/server.js +++ b/server.js @@ -69,7 +69,10 @@ app.use(bodyParser.json()); app.use('/login', cors(), (req, res)=>{ auth.login(req.body).then( (token) =>{ - res.json(token); + res.json({token: token}); + }).catch((err) =>{ + console.log(err) + res.status(500).send({error:"Wrong email or password. Please check your credentials."}) }); }) From fcd2007d23b8e86ea9f5ecad87b38605720b417c Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 29 Jan 2019 18:19:35 -0600 Subject: [PATCH 037/125] added user and role json models --- migrations/20190117003021-role.js | 54 ---- migrations/20190117003021-user.js | 54 ---- .../20190117003021-z-through-role_to_user.js | 61 ---- models/index.js | 27 -- models/role.js | 32 -- models/user.js | 32 -- resolvers/index.js | 16 - resolvers/role.js | 300 ----------------- resolvers/user.js | 305 ------------------ schemas/commons.js | 27 -- schemas/role.js | 53 --- schemas/user.js | 53 --- static-json-files/role.json | 18 ++ static-json-files/user.json | 19 ++ 14 files changed, 37 insertions(+), 1014 deletions(-) delete mode 100644 migrations/20190117003021-role.js delete mode 100644 migrations/20190117003021-user.js delete mode 100644 migrations/20190117003021-z-through-role_to_user.js delete mode 100644 models/index.js delete mode 100644 models/role.js delete mode 100644 models/user.js delete mode 100644 resolvers/index.js delete mode 100644 resolvers/role.js delete mode 100644 resolvers/user.js delete mode 100644 schemas/commons.js delete mode 100644 schemas/role.js delete mode 100644 schemas/user.js create mode 100644 static-json-files/role.json create mode 100644 static-json-files/user.json diff --git a/migrations/20190117003021-role.js b/migrations/20190117003021-role.js deleted file mode 100644 index 1e0a2d5..0000000 --- a/migrations/20190117003021-role.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -/** - * @module - Migrations to create and to undo a table correpondant to a sequelize model. - */ -module.exports = { - - /** - * up - Creates a table with the fields specified in the the createTable function. - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was created successfully, rejected otherwise. - */ - up: function(queryInterface, Sequelize) { - return queryInterface.createTable('roles', { - - id: { - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true - }, - - createdAt: { - type: Sequelize.DATE - }, - - updatedAt: { - type: Sequelize.DATE - }, - - name: { - type: Sequelize.STRING, - unique: true - }, - description: { - type: Sequelize.STRING - } - - }); - }, - - /** - * down - Deletes a table. - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was deleted successfully, rejected otherwise. - */ - down: function(queryInterface, Sequelize) { - return queryInterface.dropTable('roles'); - } - -}; diff --git a/migrations/20190117003021-user.js b/migrations/20190117003021-user.js deleted file mode 100644 index 701e36d..0000000 --- a/migrations/20190117003021-user.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -/** - * @module - Migrations to create and to undo a table correpondant to a sequelize model. - */ -module.exports = { - - /** - * up - Creates a table with the fields specified in the the createTable function. - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was created successfully, rejected otherwise. - */ - up: function(queryInterface, Sequelize) { - return queryInterface.createTable('users', { - - id: { - type: Sequelize.INTEGER, - autoIncrement: true, - primaryKey: true - }, - - createdAt: { - type: Sequelize.DATE - }, - - updatedAt: { - type: Sequelize.DATE - }, - - email: { - type: Sequelize.STRING, - unique: true - }, - password: { - type: Sequelize.STRING - } - - }); - }, - - /** - * down - Deletes a table. - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was deleted successfully, rejected otherwise. - */ - down: function(queryInterface, Sequelize) { - return queryInterface.dropTable('users'); - } - -}; diff --git a/migrations/20190117003021-z-through-role_to_user.js b/migrations/20190117003021-z-through-role_to_user.js deleted file mode 100644 index 0235f45..0000000 --- a/migrations/20190117003021-z-through-role_to_user.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -/** - * @module - Migrations to creates a through table correspondant to manay-to-many association between two sequelize models. - */ -module.exports = { - - /** - * up - Creates a table in the database for storing a many-to-many association - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was succesfully created. - */ - up: function(queryInterface, Sequelize) { - return queryInterface.createTable('role_to_user', { - - createdAt: { - type: Sequelize.DATE - }, - - updatedAt: { - type: Sequelize.DATE - }, - - userId: { - type: Sequelize.INTEGER, - onDelete: 'CASCADE', - references: { - model: 'users', - key: 'id' - } - }, - - roleId: { - type: Sequelize.INTEGER, - onDelete: 'CASCADE', - references: { - model: 'roles', - key: 'id' - } - } - }).then(() => { - return queryInterface.addIndex('role_to_user', ['userId']); - }).then(() => { - return queryInterface.addIndex('role_to_user', ['roleId']); - }); - }, - - /** - * down - Deletes a table in the database for storing a many-to-many association - * - * @param {object} queryInterface Used to modify the table in the database. - * @param {object} Sequelize Sequelize instance with data types included - * @return {promise} Resolved if the table was succesfully deleted. - */ - down: function(queryInterface, Sequelize) { - return queryInterface.dropTable('role_to_user'); - } - -}; \ No newline at end of file diff --git a/models/index.js b/models/index.js deleted file mode 100644 index b5d4532..0000000 --- a/models/index.js +++ /dev/null @@ -1,27 +0,0 @@ - - const fs = require('fs'); - const path = require('path') - sequelize = require('../connection'); - - var models = {}; - - //grabs all the models in your models folder, adds them to the models object - fs.readdirSync(__dirname) - .filter(function(file) { - return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); - }) - .forEach(function(file) { - var model = sequelize['import'](path.join(__dirname, file)); - models[model.name] = model; - }); - //Important: creates associations based on associations defined in associate function in the model files - Object.keys(models).forEach(function(modelName) { - if (models[modelName].associate) { - models[modelName].associate(models); - } - }); - //update tables with association (temporary, just for testing purposes) - //this part is suppose to be done in the migration file - //sequelize.sync({force: true}); - module.exports = models; - \ No newline at end of file diff --git a/models/role.js b/models/role.js deleted file mode 100644 index 57ae576..0000000 --- a/models/role.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const Sequelize = require('sequelize'); - -/** - * module - Creates a sequelize model - * - * @param {object} sequelize Sequelize instance. - * @param {object} DataTypes Allowed sequelize data types. - * @return {object} Sequelize model with associations defined - */ -module.exports = function(sequelize, DataTypes) { - var Role = sequelize.define('role', { - - name: { - type: Sequelize.STRING, - unique: true - }, - description: { - type: Sequelize.STRING - } - }); - - Role.associate = function(models) { - Role.belongsToMany(models.user, { - through: 'role_to_user', - onDelete: 'CASCADE' - }); - }; - - return Role; -}; diff --git a/models/user.js b/models/user.js deleted file mode 100644 index c740cae..0000000 --- a/models/user.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const Sequelize = require('sequelize'); - -/** - * module - Creates a sequelize model - * - * @param {object} sequelize Sequelize instance. - * @param {object} DataTypes Allowed sequelize data types. - * @return {object} Sequelize model with associations defined - */ -module.exports = function(sequelize, DataTypes) { - var User = sequelize.define('user', { - - email: { - type: Sequelize.STRING, - unique: true - }, - password: { - type: Sequelize.STRING - } - }); - - User.associate = function(models) { - User.belongsToMany(models.role, { - through: 'role_to_user', - onDelete: 'CASCADE' - }); - }; - - return User; -}; diff --git a/resolvers/index.js b/resolvers/index.js deleted file mode 100644 index 9e43574..0000000 --- a/resolvers/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const fs = require('fs'); - -let resolvers = {}; - -module.exports = resolvers; - -fs.readdirSync(__dirname) - .filter(function(file) { - return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); - }) - .forEach(function(file) { - const resolversModel = require('./' + file); - for (resolver in resolversModel) { - resolvers[resolver] = resolversModel[resolver]; - } - }); \ No newline at end of file diff --git a/resolvers/role.js b/resolvers/role.js deleted file mode 100644 index 6c55504..0000000 --- a/resolvers/role.js +++ /dev/null @@ -1,300 +0,0 @@ -/* - Resolvers for basic CRUD operations -*/ - -const role = require('../models/index').role; -const searchArg = require('../utils/search-argument'); -const fileTools = require('../utils/file-tools'); -const helper = require('../utils/helper'); -const globals = require('../config/globals'); -const checkAuthorization = require('../utils/check-authorization'); -const path = require('path') -const fs = require('fs') -const uuidv4 = require('uuidv4') - - - -/** - * role.prototype.usersFilter - Check user authorization and return certain number, specified in pagination argument, of records - * associated with the current instance, this records should also - * holds the condition of search argument, all of them sorted as specified by the order argument. - * - * @param {object} search Search argument for filtering associated records - * @param {array} order Type of sorting (ASC, DESC) for each field - * @param {object} pagination Offset and limit to get the records from and to respectively - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {array} Array of associated records holding conditions specified by search, order and pagination argument - */ -role.prototype.usersFilter = function({ - search, - order, - pagination -}, context) { - - let options = {}; - - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return this.countUsers(options).then(items => { - if (order !== undefined) { - options['order'] = order.map((orderItem) => { - return [orderItem.field, orderItem.order]; - }); - } - - if (pagination !== undefined) { - options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; - options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; - } else { - options['offset'] = 0; - options['limit'] = items; - } - - if (globals.LIMIT_RECORDS < options['limit']) { - throw new Error(`Request of total usersFilter exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); - } - return this.getUsers(options); - }).catch(error => { - console.log("Catched the error in usersFilter ", error); - return error; - }); -} - -/** - * role.prototype.countFilteredUsers - Count number of associated records that holds the conditions specified in the search argument - * - * @param {object} {search} description - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {type} Number of associated records that holds the conditions specified in the search argument - */ -role.prototype.countFilteredUsers = function({ - search -}, context) { - - let options = {}; - - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return this.countUsers(options); -} - - - - -module.exports = { - - /** - * roles - Check user authorization and return certain number, specified in pagination argument, of records that - * holds the condition of search argument, all of them sorted as specified by the order argument. - * - * @param {object} search Search argument for filtering records - * @param {array} order Type of sorting (ASC, DESC) for each field - * @param {object} pagination Offset and limit to get the records from and to respectively - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {array} Array of records holding conditions specified by search, order and pagination argument - */ - roles: function({ - search, - order, - pagination - }, context) { - if (checkAuthorization(context, 'roles', 'read') == true) { - let options = {}; - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return role.count(options).then(items => { - if (order !== undefined) { - options['order'] = order.map((orderItem) => { - return [orderItem.field, orderItem.order]; - }); - } - - if (pagination !== undefined) { - options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; - options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; - } else { - options['offset'] = 0; - options['limit'] = items; - } - - if (globals.LIMIT_RECORDS < options['limit']) { - throw new Error(`Request of total roles exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); - } - return role.findAll(options); - }).catch(error => { - console.log("Catched the error in roles ", error); - return error; - }); - } else { - return new Error("You don't have authorization to perform this action"); - } - }, - - /** - * readOneRole - Check user authorization and return one book with the specified id in the id argument. - * - * @param {number} {id} Id of the record to retrieve - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Record with id requested - */ - readOneRole: function({ - id - }, context) { - if (checkAuthorization(context, 'roles', 'read') == true) { - return role.findOne({ - where: { - id: id - } - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * addRole - Check user authorization and creates a new record with data specified in the input argument - * - * @param {object} input Info of each field to create the new record - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} New record created - */ - addRole: function(input, context) { - if (checkAuthorization(context, 'roles', 'create') == true) { - return role.create(input) - .then(role => { - if (input.addUsers) { - role.setUsers(input.addUsers); - } - return role; - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * bulkAddRoleXlsx - Load xlsx file of records - * - * @param {string} _ First parameter is not used - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - */ - bulkAddRoleXlsx: function(_, context) { - let xlsxObjs = fileTools.parseXlsx(context.request.files.xlsx_file.data.toString('binary')); - return role.bulkCreate(xlsxObjs, { - validate: true - }); - }, - - /** - * bulkAddRoleCsv - Load csv file of records - * - * @param {string} _ First parameter is not used - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - */ - bulkAddRoleCsv: function(_, context) { - delim = context.request.body.delim; - cols = context.request.body.cols; - tmpFile = path.join(__dirname, uuidv4() + '.csv') - return context.request.files.csv_file.mv(tmpFile).then(() => { - return fileTools.parseCsvStream(tmpFile, role, delim, cols) - }).catch((err) => { - return new Error(err); - }).then(() => { - fs.unlinkSync(tmpFile) - }) - }, - - /** - * deleteRole - Check user authorization and delete a record with the specified id in the id argument. - * - * @param {number} {id} Id of the record to delete - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {string} Message indicating if deletion was successfull. - */ - deleteRole: function({ - id - }, context) { - if (checkAuthorization(context, 'roles', 'delete') == true) { - return role.findById(id) - .then(role => { - return role.destroy() - .then(() => { - return 'Item succesfully deleted'; - }); - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * updateRole - Check user authorization and update the record specified in the input argument - * - * @param {object} input record to update and new info to update - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Updated record - */ - updateRole: function(input, context) { - if (checkAuthorization(context, 'roles', 'update') == true) { - return role.findById(input.id) - .then(role => { - if (input.addUsers) { - role.addUsers(input.addUsers); - } - if (input.removeUsers) { - role.removeUsers(input.removeUsers); - } - return role.update(input); - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * countRoles - Count number of records that holds the conditions specified in the search argument - * - * @param {object} {search} Search argument for filtering records - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {number} Number of records that holds the conditions specified in the search argument - */ - countRoles: function({ - search - }, context) { - let options = {}; - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return role.count(options); - }, - - /** - * vueTableRole - Return table of records as needed for displaying a vuejs table - * - * @param {string} _ First parameter is not used - * @param {type} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Records with format as needed for displaying a vuejs table - */ - vueTableRole: function(_, context) { - if (checkAuthorization(context, 'roles', 'read') == true) { - return helper.vueTable(context.request, role, ["id", "name", "description"]); - } else { - return "You don't have authorization to perform this action"; - } - } -} \ No newline at end of file diff --git a/resolvers/user.js b/resolvers/user.js deleted file mode 100644 index fba4d92..0000000 --- a/resolvers/user.js +++ /dev/null @@ -1,305 +0,0 @@ -/* - Resolvers for basic CRUD operations -*/ - -const user = require('../models/index').user; -const searchArg = require('../utils/search-argument'); -const fileTools = require('../utils/file-tools'); -const helper = require('../utils/helper'); -const globals = require('../config/globals'); -const checkAuthorization = require('../utils/check-authorization'); -const path = require('path') -const fs = require('fs') -const uuidv4 = require('uuidv4') - - - -/** - * user.prototype.rolesFilter - Check user authorization and return certain number, specified in pagination argument, of records - * associated with the current instance, this records should also - * holds the condition of search argument, all of them sorted as specified by the order argument. - * - * @param {object} search Search argument for filtering associated records - * @param {array} order Type of sorting (ASC, DESC) for each field - * @param {object} pagination Offset and limit to get the records from and to respectively - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {array} Array of associated records holding conditions specified by search, order and pagination argument - */ -user.prototype.rolesFilter = function({ - search, - order, - pagination -}, context) { - - let options = {}; - - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return this.countRoles(options).then(items => { - if (order !== undefined) { - options['order'] = order.map((orderItem) => { - return [orderItem.field, orderItem.order]; - }); - } - - if (pagination !== undefined) { - options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; - options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; - } else { - options['offset'] = 0; - options['limit'] = items; - } - - if (globals.LIMIT_RECORDS < options['limit']) { - throw new Error(`Request of total rolesFilter exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); - } - return this.getRoles(options); - }).catch(error => { - console.log("Catched the error in rolesFilter ", error); - return error; - }); -} - -/** - * user.prototype.countFilteredRoles - Count number of associated records that holds the conditions specified in the search argument - * - * @param {object} {search} description - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {type} Number of associated records that holds the conditions specified in the search argument - */ -user.prototype.countFilteredRoles = function({ - search -}, context) { - - let options = {}; - - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return this.countRoles(options); -} - - - - -module.exports = { - - /** - * users - Check user authorization and return certain number, specified in pagination argument, of records that - * holds the condition of search argument, all of them sorted as specified by the order argument. - * - * @param {object} search Search argument for filtering records - * @param {array} order Type of sorting (ASC, DESC) for each field - * @param {object} pagination Offset and limit to get the records from and to respectively - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {array} Array of records holding conditions specified by search, order and pagination argument - */ - users: function({ - search, - order, - pagination - }, context) { - - return checkAuthorization(context, 'users', 'read').then( authorization =>{ - if (authorization === true) { - let options = {}; - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return user.count(options).then(items => { - if (order !== undefined) { - options['order'] = order.map((orderItem) => { - return [orderItem.field, orderItem.order]; - }); - } - - if (pagination !== undefined) { - options['offset'] = pagination.offset === undefined ? 0 : pagination.offset; - options['limit'] = pagination.limit === undefined ? (items - options['offset']) : pagination.limit; - } else { - options['offset'] = 0; - options['limit'] = items; - } - - if (globals.LIMIT_RECORDS < options['limit']) { - throw new Error(`Request of total users exceeds max limit of ${globals.LIMIT_RECORDS}. Please use pagination.`); - } - return user.findAll(options); - }).catch(error => { - console.log("Catched the error in users ", error); - return error; - }); - } else { - return new Error("You don't have authorization to perform this action"); - } - }).catch( error =>{ - return error; - }) - }, - - /** - * readOneUser - Check user authorization and return one book with the specified id in the id argument. - * - * @param {number} {id} Id of the record to retrieve - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Record with id requested - */ - readOneUser: function({ - id - }, context) { - if (checkAuthorization(context, 'users', 'read') == true) { - return user.findOne({ - where: { - id: id - } - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * addUser - Check user authorization and creates a new record with data specified in the input argument - * - * @param {object} input Info of each field to create the new record - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} New record created - */ - addUser: function(input, context) { - if (checkAuthorization(context, 'users', 'create') == true) { - return user.create(input) - .then(user => { - if (input.addRoles) { - user.setRoles(input.addRoles); - } - return user; - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * bulkAddUserXlsx - Load xlsx file of records - * - * @param {string} _ First parameter is not used - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - */ - bulkAddUserXlsx: function(_, context) { - let xlsxObjs = fileTools.parseXlsx(context.request.files.xlsx_file.data.toString('binary')); - return user.bulkCreate(xlsxObjs, { - validate: true - }); - }, - - /** - * bulkAddUserCsv - Load csv file of records - * - * @param {string} _ First parameter is not used - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - */ - bulkAddUserCsv: function(_, context) { - delim = context.request.body.delim; - cols = context.request.body.cols; - tmpFile = path.join(__dirname, uuidv4() + '.csv') - return context.request.files.csv_file.mv(tmpFile).then(() => { - return fileTools.parseCsvStream(tmpFile, user, delim, cols) - }).catch((err) => { - return new Error(err); - }).then(() => { - fs.unlinkSync(tmpFile) - }) - }, - - /** - * deleteUser - Check user authorization and delete a record with the specified id in the id argument. - * - * @param {number} {id} Id of the record to delete - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {string} Message indicating if deletion was successfull. - */ - deleteUser: function({ - id - }, context) { - if (checkAuthorization(context, 'users', 'delete') == true) { - return user.findById(id) - .then(user => { - return user.destroy() - .then(() => { - return 'Item succesfully deleted'; - }); - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * updateUser - Check user authorization and update the record specified in the input argument - * - * @param {object} input record to update and new info to update - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Updated record - */ - updateUser: function(input, context) { - if (checkAuthorization(context, 'users', 'update') == true) { - return user.findById(input.id) - .then(user => { - if (input.addRoles) { - user.addRoles(input.addRoles); - } - if (input.removeRoles) { - user.removeRoles(input.removeRoles); - } - return user.update(input); - }); - } else { - return "You don't have authorization to perform this action"; - } - }, - - /** - * countUsers - Count number of records that holds the conditions specified in the search argument - * - * @param {object} {search} Search argument for filtering records - * @param {object} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {number} Number of records that holds the conditions specified in the search argument - */ - countUsers: function({ - search - }, context) { - let options = {}; - if (search !== undefined) { - let arg = new searchArg(search); - let arg_sequelize = arg.toSequelize(); - options['where'] = arg_sequelize; - } - - return user.count(options); - }, - - /** - * vueTableUser - Return table of records as needed for displaying a vuejs table - * - * @param {string} _ First parameter is not used - * @param {type} context Provided to every resolver holds contextual information like the resquest query and user info. - * @return {object} Records with format as needed for displaying a vuejs table - */ - vueTableUser: function(_, context) { - if (checkAuthorization(context, 'users', 'read') == true) { - return helper.vueTable(context.request, user, ["id", "email", "password"]); - } else { - return "You don't have authorization to perform this action"; - } - } -} diff --git a/schemas/commons.js b/schemas/commons.js deleted file mode 100644 index 4cacc60..0000000 --- a/schemas/commons.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = ` - - enum Operator{ - like - or - and - eq - between - in - } - - enum Order{ - DESC - ASC - } - - input typeValue{ - type: String - value: String! - } - - input paginationInput{ - limit: Int - offset: Int - } - -`; \ No newline at end of file diff --git a/schemas/role.js b/schemas/role.js deleted file mode 100644 index 7c9fc00..0000000 --- a/schemas/role.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = ` - type Role { - id: ID - name: String - description: String - usersFilter(search: searchUserInput, order: [ orderUserInput ], pagination: paginationInput): [User] - countFilteredUsers(search: searchUserInput) : Int - } - - type VueTableRole{ - data : [Role] - total: Int - per_page: Int - current_page: Int - last_page: Int - prev_page_url: String - next_page_url: String - from: Int - to: Int - } - - enum RoleField { - id - name - description - } - - input searchRoleInput { - field: RoleField - value: typeValue - operator: Operator - search: [searchRoleInput] - } - - input orderRoleInput{ - field: RoleField - order: Order - } - - type Query { - roles(search: searchRoleInput, order: [ orderRoleInput ], pagination: paginationInput ): [Role] - readOneRole(id: ID!): Role - countRoles(search: searchRoleInput ): Int - vueTableRole : VueTableRole } - - type Mutation { - addRole( name: String, description: String , addUsers:[ID] ): Role - deleteRole(id: ID!): String! - updateRole(id: ID!, name: String, description: String , addUsers:[ID], removeUsers:[ID] ): Role! - bulkAddRoleXlsx: [Role] - bulkAddRoleCsv: [Role] -} - `; \ No newline at end of file diff --git a/schemas/user.js b/schemas/user.js deleted file mode 100644 index 44d094b..0000000 --- a/schemas/user.js +++ /dev/null @@ -1,53 +0,0 @@ -module.exports = ` - type User { - id: ID - email: String - password: String - rolesFilter(search: searchRoleInput, order: [ orderRoleInput ], pagination: paginationInput): [Role] - countFilteredRoles(search: searchRoleInput) : Int - } - - type VueTableUser{ - data : [User] - total: Int - per_page: Int - current_page: Int - last_page: Int - prev_page_url: String - next_page_url: String - from: Int - to: Int - } - - enum UserField { - id - email - password - } - - input searchUserInput { - field: UserField - value: typeValue - operator: Operator - search: [searchUserInput] - } - - input orderUserInput{ - field: UserField - order: Order - } - - type Query { - users(search: searchUserInput, order: [ orderUserInput ], pagination: paginationInput ): [User] - readOneUser(id: ID!): User - countUsers(search: searchUserInput ): Int - vueTableUser : VueTableUser } - - type Mutation { - addUser( email: String, password: String , addRoles:[ID] ): User - deleteUser(id: ID!): String! - updateUser(id: ID!, email: String, password: String , addRoles:[ID], removeRoles:[ID] ): User! - bulkAddUserXlsx: [User] - bulkAddUserCsv: [User] -} - `; \ No newline at end of file diff --git a/static-json-files/role.json b/static-json-files/role.json new file mode 100644 index 0000000..a374c61 --- /dev/null +++ b/static-json-files/role.json @@ -0,0 +1,18 @@ +{ + "model" : "Role", + "storageType" : "SQL", + "attributes" : { + "name" : "String", + "description" : "String" + }, + "associations" : { + "users" : { + "type" : "sql_belongsToMany", + "target" : "User", + "targetKey" : "userId", + "sourceKey" : "roleId", + "keysIn" : "role_to_user", + "targetStorageType" : "sql" + } + } +} diff --git a/static-json-files/user.json b/static-json-files/user.json new file mode 100644 index 0000000..89915c5 --- /dev/null +++ b/static-json-files/user.json @@ -0,0 +1,19 @@ +{ + "model" : "User", + "storageType" : "SQL", + "attributes" : { + "email" : "String", + "password" : "String" + }, + "associations" :{ + "roles" : { + "type" : "sql_belongsToMany", + "target" : "Role", + "targetKey" : "roleId", + "sourceKey" : "userId", + "keysIn" : "role_to_user", + "targetStorageType" : "sql" + } + } + +} From b968a78262c9c7ecc64f6b22f80a4f612c1e00b8 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 29 Jan 2019 18:28:08 -0600 Subject: [PATCH 038/125] gitignore output folders --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 195cbfd..268c001 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,9 @@ node_modules npm-debug.log package-lock.json - +# ModelGen output folders +migrations +models +models-webservice +resolvers +schemas From c7f321b7177e41f68cbccc1a2b3978dca943521f Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 1 Feb 2019 11:31:52 -0600 Subject: [PATCH 039/125] fix authorization function when no acl provided --- utils/check-authorization.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/check-authorization.js b/utils/check-authorization.js index 44441d2..ae31364 100644 --- a/utils/check-authorization.js +++ b/utils/check-authorization.js @@ -14,7 +14,10 @@ const secret = 'something-secret'; */ module.exports = function( context, resource, permission ) { //if there's not authorization rules set - if (context.acl == null) return true; + if (context.acl == null) //return true; + { + return Promise.resolve(true); + } let token_bearer = context.request.headers["authorization"]; let token = token_bearer.replace("Bearer ",""); From 81f8d9f8b9cf7a1c9b1a1bafdbdf81ad57f7e949 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Tue, 5 Feb 2019 05:14:11 -0600 Subject: [PATCH 040/125] added 'sync-request' dependency for webservices --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f94b78..c8894d1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", "uuidv4": "^2.0.0", - "xlsx": "^0.12.11" + "xlsx": "^0.12.11", + "sync-request": "^6.0.0" }, "devDependencies": { "mocha": "^5.2.0" From 85686d751d75885855fadd3f1f35fa12be7951ff Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Wed, 13 Feb 2019 17:36:44 -0600 Subject: [PATCH 041/125] static models moved to generator --- static-json-files/role.json | 18 ------------------ static-json-files/user.json | 19 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 static-json-files/role.json delete mode 100644 static-json-files/user.json diff --git a/static-json-files/role.json b/static-json-files/role.json deleted file mode 100644 index a374c61..0000000 --- a/static-json-files/role.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "model" : "Role", - "storageType" : "SQL", - "attributes" : { - "name" : "String", - "description" : "String" - }, - "associations" : { - "users" : { - "type" : "sql_belongsToMany", - "target" : "User", - "targetKey" : "userId", - "sourceKey" : "roleId", - "keysIn" : "role_to_user", - "targetStorageType" : "sql" - } - } -} diff --git a/static-json-files/user.json b/static-json-files/user.json deleted file mode 100644 index 89915c5..0000000 --- a/static-json-files/user.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "model" : "User", - "storageType" : "SQL", - "attributes" : { - "email" : "String", - "password" : "String" - }, - "associations" :{ - "roles" : { - "type" : "sql_belongsToMany", - "target" : "Role", - "targetKey" : "roleId", - "sourceKey" : "userId", - "keysIn" : "role_to_user", - "targetStorageType" : "sql" - } - } - -} From 9144f7ef447b3fabd6d20f1f40aafb43b1d9d6ee Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 19 Feb 2019 15:30:04 -0600 Subject: [PATCH 042/125] custom error in graphql server --- server.js | 7 +++++++ utils/errors.js | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 utils/errors.js diff --git a/server.js b/server.js index 90eb79a..2a94d80 100644 --- a/server.js +++ b/server.js @@ -87,6 +87,13 @@ app.use('/login', cors(), (req, res)=>{ context: { request: req, acl: acl + }, + formatError(error){ + return { + message: error.message, + details: error.originalError.errors, + path: error.path + }; } }))); diff --git a/utils/errors.js b/utils/errors.js new file mode 100644 index 0000000..dd9b7ba --- /dev/null +++ b/utils/errors.js @@ -0,0 +1,26 @@ + +class customArrayError extends Error { + constructor(errors_array, message){ + super(); + this.message = message; + this.errors = errors_array; + } +} + +class otherError extends Error { + constructor(oneError,message){ + super(); + this.message = message; + this.detail = oneError; + } +} + +handleError = function(error){ + if(error.name === "SequelizeValidationError"){ + throw new customArrayError(error.errors, "Validation error"); + }else{ + throw new Error(error) + } +} + +module.exports = { handleError} From d7e376975d593611866d1384e10ca676deea0e80 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 19 Feb 2019 16:03:52 -0600 Subject: [PATCH 043/125] fix bug when originalError does not exist --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 2a94d80..6603b1d 100644 --- a/server.js +++ b/server.js @@ -91,7 +91,7 @@ app.use('/login', cors(), (req, res)=>{ formatError(error){ return { message: error.message, - details: error.originalError.errors, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", path: error.path }; } From bd2eb3f11dd296cb679e3b41a869ead4b8623edf Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 20 Feb 2019 22:54:26 -0600 Subject: [PATCH 044/125] clean code --- utils/errors.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/utils/errors.js b/utils/errors.js index dd9b7ba..00ab446 100644 --- a/utils/errors.js +++ b/utils/errors.js @@ -7,18 +7,15 @@ class customArrayError extends Error { } } -class otherError extends Error { - constructor(oneError,message){ - super(); - this.message = message; - this.detail = oneError; - } -} - handleError = function(error){ if(error.name === "SequelizeValidationError"){ - throw new customArrayError(error.errors, "Validation error"); + console.log("Validation error") + let errorC = new customArrayError(error.errors, "Validation error"); + console.log( errorC ); + throw errorC; + }else{ + console.log('Other error') throw new Error(error) } } From 036c97767dd536183f7412fff66e1a10aac9e534 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 21 Feb 2019 00:24:20 -0600 Subject: [PATCH 045/125] delete log commands --- utils/errors.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/utils/errors.js b/utils/errors.js index 00ab446..f0eae8a 100644 --- a/utils/errors.js +++ b/utils/errors.js @@ -9,13 +9,8 @@ class customArrayError extends Error { handleError = function(error){ if(error.name === "SequelizeValidationError"){ - console.log("Validation error") - let errorC = new customArrayError(error.errors, "Validation error"); - console.log( errorC ); - throw errorC; - + throw new customArrayError(error.errors, "Validation error"); }else{ - console.log('Other error') throw new Error(error) } } From bbde7549178e432b8551ee578f3f5a5ed61d4beb Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Mon, 25 Feb 2019 19:45:38 -0600 Subject: [PATCH 046/125] Closed #50 creation in the background and notify user via email --- .gitignore | 1 + config/globals.js | 7 ++++++- package.json | 8 ++++++-- utils/email.js | 34 +++++++++++++++++++++++++++++++++ utils/file-tools.js | 45 ++++++++++++++++++++++++++++++-------------- utils/helpers-acl.js | 22 ++++++++++++++++++++++ 6 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 utils/email.js create mode 100644 utils/helpers-acl.js diff --git a/.gitignore b/.gitignore index 268c001..915efe0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ models models-webservice resolvers schemas +test \ No newline at end of file diff --git a/config/globals.js b/config/globals.js index 9a44676..96616dd 100644 --- a/config/globals.js +++ b/config/globals.js @@ -1,3 +1,8 @@ module.exports = { - LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000 + LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, + + MAIL_SERVICE: "gmail", + MAIL_HOST: "smtp.gmail.com", + MAIL_ACCOUNT: "sci.db.service@gmail.com", + MAIL_PASSWORD: "SciDbServiceQAZ" } diff --git a/package.json b/package.json index c8894d1..7a1600d 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,13 @@ "supertest": "^3.1.0", "uuidv4": "^2.0.0", "xlsx": "^0.12.11", - "sync-request": "^6.0.0" + "sync-request": "^6.0.0", + "nodemailer" : "^5.1.1", + "nodemailer-smtp-transport": "^2.7.4" }, "devDependencies": { - "mocha": "^5.2.0" + "mocha": "^5.2.0", + "axios": "^0.18.0", + "form-data": "^2.3.2-rc1" } } diff --git a/utils/email.js b/utils/email.js new file mode 100644 index 0000000..afbfb08 --- /dev/null +++ b/utils/email.js @@ -0,0 +1,34 @@ +const NodeMailer = require('nodemailer'); +const SmtpTransport = require('nodemailer-smtp-transport'); +const Globals = require('../config/globals'); + +module.exports = { + sendEmail: function (dst_email, subject, message){ + console.log(`${dst_email}, ${message}, ${Globals.MAIL_ACCOUNT}, ${Globals.MAIL_PASSWORD}`); + + let transporter = NodeMailer.createTransport(SmtpTransport({ + service: Globals.MAIL_SERVICE, + host: Globals.MAIL_HOST, + auth: { + type: "login", + user: Globals.MAIL_ACCOUNT, + pass: Globals.MAIL_PASSWORD + } + })); + + let mailOptions = { + from: Globals.MAIL_ACCOUNT, + to: dst_email, + subject: subject, + text: message + }; + + transporter.sendMail(mailOptions, function(error, info){ + if (error) { + console.log(error); + } else { + console.log('Email sent: ' + info.response); + } + }); + } +} \ No newline at end of file diff --git a/utils/file-tools.js b/utils/file-tools.js index 9127a94..dc13ed6 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -71,11 +71,11 @@ exports.parseXlsx = function(bstr) { * @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). */ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { - if (!delim) delim = "," - if (typeof cols === 'undefined') cols = true - console.log("TYPEOF", typeof model) + if (!delim) delim = ","; + if (typeof cols === 'undefined') cols = true; + console.log("TYPEOF", typeof model); // Wrap all database actions within a transaction: - let transaction = await model.sequelize.transaction() + let transaction = await model.sequelize.transaction(); try { // Pipe a file read-stream through a CSV-Reader and make the records // handleable asynchronously: @@ -88,22 +88,39 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { ) ) - let record + let record; + let errors = []; + while (null !== (record = await csvStream.readAsync())) { - console.log(`Read record: ${JSON.stringify(record)}`) + + console.log(`Read record: ${JSON.stringify(record)}`); await model.create(record, { transaction: transaction + }).catch(error => { + console.log(`Caught error in while-loop: ${JSON.stringify(error)}`) - // Enable identification of those rows / records that caused validation - // errors: - error.record = record - throw error + error.record = record; + errors.push(error); + }) } - await transaction.commit() + + if(errors.length > 0) { + let message = "Some records could not be submitted. No database changes has been applied.\n"; + message += "Please see the next list for details:\n"; + + errors.forEach(function(error) { + message += `record ${JSON.stringify(error.record)} ${error.message}; \n`; + }); + + throw new Error(message.slice(0, message.length-1)); + } + + await transaction.commit(); + } catch (error) { - await transaction.rollback() - throw error + await transaction.rollback(); + throw error; } -} +}; diff --git a/utils/helpers-acl.js b/utils/helpers-acl.js new file mode 100644 index 0000000..fa930a9 --- /dev/null +++ b/utils/helpers-acl.js @@ -0,0 +1,22 @@ +const secret = 'something-secret'; +const jwt = require('jsonwebtoken'); + +//TODO: Use this routines through all the code to have them in one place + +module.exports = { + getTokenFromContext: function (context) { + let token_bearer = context.request.headers["authorization"]; + let token = token_bearer.replace("Bearer ",""); + let decoded = jwt.verify(token, secret); + return decoded; + }, + + //INFO: can be useful for tests + /*generateDummyToken: function (email) { + return jsonwebtoken.sign({ + id: 1, + email: email, + roles: "admin" + }, 'something-secret', { expiresIn: '1h' }); + }*/ +}; \ No newline at end of file From 5c3e37265859a80328d315bc579fcb013d17a003 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 26 Feb 2019 02:08:01 -0600 Subject: [PATCH 047/125] send error unaunthorized middleware - set origin-allow --- server.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/server.js b/server.js index 6603b1d..6525d53 100644 --- a/server.js +++ b/server.js @@ -19,6 +19,22 @@ const APP_PORT = 3000; const app = express(); + app.use((req, res, next)=> { + + // Website you wish to allow to connect + res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); + //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); + + // Request methods you wish to allow + //res.setHeader('Access-Control-Allow-Methods', + // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + + // Request headers you wish to allow + //res.setHeader('Access-Control-Allow-Headers', + // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); + next(); + }); + /* Temporary solution: acl rules set */ if (process.argv.length > 2 && process.argv[2] == 'acl') { var node_acl = require('acl'); @@ -47,21 +63,6 @@ console.log(merged_schema) - //app.use((req, res, next)=> { - - // Website you wish to allow to connect - //res.setHeader('Access-Control-Allow-Origin', '*'); - //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); - - // Request methods you wish to allow - //res.setHeader('Access-Control-Allow-Methods', - // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); - - // Request headers you wish to allow - //res.setHeader('Access-Control-Allow-Headers', - // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); - // next(); - //}); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); @@ -78,7 +79,7 @@ app.use('/login', cors(), (req, res)=>{ }) app.use(fileUpload()); - /*request is passed as context by default */ + /*request is passed as context by default */ app.use('/graphql', cors(), graphqlHTTP((req) => ({ schema: Schema, rootValue: resolvers, @@ -97,6 +98,15 @@ app.use('/login', cors(), (req, res)=>{ } }))); + // Error handling + app.use(function (err, req, res, next) { + if (err.name === 'UnauthorizedError') { // Send the error rather than to show it on the console + res.status(401).send(err); + } + else { + next(err); + } + }); var server = app.listen(APP_PORT, () => { console.log(`App listening on port ${APP_PORT}`); From 24e61e6d096c61851b400eb83218dbb843734e7c Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Tue, 5 Mar 2019 00:53:01 -0600 Subject: [PATCH 048/125] Closed #53 sequelize independent validations --- .gitignore | 4 +++- package.json | 3 ++- utils/validatorUtil.js | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 utils/validatorUtil.js diff --git a/.gitignore b/.gitignore index 915efe0..4ad0f13 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ models models-webservice resolvers schemas -test \ No newline at end of file +test +validations +patches \ No newline at end of file diff --git a/package.json b/package.json index 7a1600d..adee4c5 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "xlsx": "^0.12.11", "sync-request": "^6.0.0", "nodemailer" : "^5.1.1", - "nodemailer-smtp-transport": "^2.7.4" + "nodemailer-smtp-transport": "^2.7.4", + "joi" : "^14.3.1" }, "devDependencies": { "mocha": "^5.2.0", diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js new file mode 100644 index 0000000..8df9caf --- /dev/null +++ b/utils/validatorUtil.js @@ -0,0 +1,18 @@ + +/** + * ifHasValidatorFunctionInvoke - checks if data model has a validator function with + * the given name, and apply that function + * + * @param {string} validatorFunction Name of the validator function + * @param {object} dataModel The empty data model object + * @param {object} data JSON data to be inserted into the dataModel + * @return {Error} An error object if data is invalid or null otherwise + * + */ + +module.exports.ifHasValidatorFunctionInvoke = function( validatorFunction, dataModel, data) { + if (typeof dataModel.prototype[validatorFunction] === "function") { + return dataModel.prototype[validatorFunction](data).error; + } +}; + From aac14546fead36aa660090df362cd4c9fcdbdcd6 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Tue, 5 Mar 2019 02:08:48 -0600 Subject: [PATCH 049/125] Closed #50 creation in the background and notify user via email --- package-lock.json | 2858 ---------------------------------------- utils/validatorUtil.js | 6 +- 2 files changed, 5 insertions(+), 2859 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 316b1c0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2858 +0,0 @@ -{ - "name": "second-server-graphql", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/geojson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" - }, - "@types/node": { - "version": "9.4.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz", - "integrity": "sha512-CTUtLb6WqCCgp6P59QintjHWqzf4VL1uPA27bipLAPxFqrtK1gEYllePzTICGqQ8rYsCbpnsNypXjjDzGAAjEQ==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "2.1.18", - "negotiator": "0.6.1" - } - }, - "acl": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/acl/-/acl-0.4.11.tgz", - "integrity": "sha1-ACzHZuvyXNqP5TK1bzZRvr2yWzo=", - "requires": { - "async": "2.6.0", - "bluebird": "3.5.1", - "lodash": "4.17.5", - "mongodb": "2.2.35", - "redis": "2.8.0" - } - }, - "adler-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", - "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", - "requires": { - "exit-on-epipe": "1.0.1", - "printj": "1.1.2" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" - }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "4.17.5" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "awaitify-stream": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/awaitify-stream/-/awaitify-stream-1.0.2.tgz", - "integrity": "sha512-JE6mrRIPxhBQWt9Mu4u2XJF9V9xp4p5+Uxif/Ol/s2TFC/9+offUE50j/KQdaVWwMyGlKrlsE7Ncq/yhc6AJ+w==" - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "2.5.5", - "regenerator-runtime": "0.11.1" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" - }, - "bcrypt": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.3.tgz", - "integrity": "sha512-4EuzUo6K790QC3uq/ogzy9w2Hc7XDIBoEndU5y7l7YaEAwQF8vyFqv6tC30+gOBZvyxk3F632xzKBQoLNz2pjg==", - "requires": { - "nan": "2.12.1", - "node-pre-gyp": "0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.5" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "requires": { - "minipass": "2.3.4" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.3.4", - "bundled": true, - "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "minizlib": { - "version": "1.1.1", - "bundled": true, - "requires": { - "minipass": "2.3.4" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true - }, - "needle": { - "version": "2.2.4", - "bundled": true, - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.24", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.4", - "nopt": "4.0.1", - "npm-packlist": "1.1.12", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.6.2", - "semver": "5.6.0", - "tar": "4.4.8" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true - }, - "npm-packlist": { - "version": "1.1.12", - "bundled": true, - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.5" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } - }, - "readable-stream": { - "version": "2.3.5", - "bundled": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "sax": { - "version": "1.2.4", - "bundled": true - }, - "semver": { - "version": "5.6.0", - "bundled": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "bundled": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "requires": { - "chownr": "1.1.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.4", - "minizlib": "1.1.1", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - } - } - }, - "bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "bson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz", - "integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ==" - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" - }, - "buffer-writer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", - "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", - "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.14" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "cfb": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.0.7.tgz", - "integrity": "sha512-KjjZFR+a/e8RDdDTr4PwR0P/HIFRI3sxArFQttml0pFkhIO4TnvS/1+dqtGXPqe5/0MHp2IzjFx1JTzmohHT+w==", - "requires": { - "commander": "2.15.1", - "printj": "1.1.2" - } - }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "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", - "type-detect": "4.0.8" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" - }, - "cli-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", - "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", - "requires": { - "ansi-regex": "2.1.1", - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "memoizee": "0.4.12", - "timers-ext": "0.1.5" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "cls-bluebird": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", - "requires": { - "is-bluebird": "1.0.2", - "shimmer": "1.2.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "codepage": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.12.2.tgz", - "integrity": "sha512-FAN+oPs/ocaPLFvIt4vEOHgWA6UJ6t+fVbbVBoXDpTpC+4JYasomYZEEjR/Miph3qQrVnIShRwwmwu4P35JW1w==", - "requires": { - "commander": "2.14.1", - "exit-on-epipe": "1.0.1" - }, - "dependencies": { - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - } - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "complex.js": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", - "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", - "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" - } - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" - }, - "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cors": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", - "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", - "requires": { - "object-assign": "4.1.1", - "vary": "1.1.2" - } - }, - "crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "requires": { - "exit-on-epipe": "1.0.1", - "printj": "1.1.2" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "csv-parse": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-2.2.0.tgz", - "integrity": "sha512-nNXh61kEIUbTXPWPZbrKlkkylh7BDxffDUWQPWIho+Rog4XWRV8bTR8ZVo8qngzAwbhlvtKFcsaf2hGDV1iF8Q==" - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "requires": { - "es5-ext": "0.10.42" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decimal.js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", - "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "requires": { - "type-detect": "4.0.8" - } - }, - "deepmerge": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz", - "integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "denque": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.3.0.tgz", - "integrity": "sha512-4SRaSj+PqmrS1soW5/Avd7eJIM2JJIqLLmwhRqIGleZM/8KwZq80njbSS2Iqas+6oARkSkLDHEk4mm78q3JlIg==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", - "requires": { - "readable-stream": "1.1.14", - "streamsearch": "0.1.2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dottie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", - "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" - }, - "double-ended-queue": { - "version": "2.1.0-0", - "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" - }, - "ecdsa-sig-formatter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", - "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", - "requires": { - "base64url": "2.0.0", - "safe-buffer": "5.1.1" - } - }, - "editorconfig": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", - "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", - "requires": { - "bluebird": "3.5.1", - "commander": "2.15.1", - "lru-cache": "3.2.0", - "semver": "5.5.0", - "sigmund": "1.0.1" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-symbol": "3.1.1" - } - }, - "es6-promise": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-latex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.1.1.tgz", - "integrity": "sha512-N2D6Z2kXh8x/pQNQH+natXDCwrzghhXMRII5dZ518mlTLeuba80NL0LCQyaahqOrAidoLivmmG6GKPnGhHse+A==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, - "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", - "requires": { - "accepts": "1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.16", - "utils-merge": "1.0.1", - "vary": "1.1.2" - } - }, - "express-fileupload": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-0.4.0.tgz", - "integrity": "sha512-jPv3aCdTIdQrGAUXQ1e1hU0Vnl+0jE9IbzEsI7VRIevQybrUrIMUgvwNwBThnsetandW8+9ICgflAkhKwLUuLw==", - "requires": { - "busboy": "0.2.14", - "fs-extra": "4.0.3", - "md5": "2.2.1", - "streamifier": "0.1.1" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - } - } - }, - "express-graphql": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.6.12.tgz", - "integrity": "sha512-ouLWV0hRw4hnaLtXzzwhdC79ewxKbY2PRvm05mPc/zOH5W5WVCHDQ1SmNxEPBQdUeeSNh29aIqW9zEQkA3kMuA==", - "requires": { - "accepts": "1.3.5", - "content-type": "1.0.4", - "http-errors": "1.6.2", - "raw-body": "2.3.2" - } - }, - "express-jwt": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", - "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", - "requires": { - "async": "1.5.2", - "express-unless": "0.3.1", - "jsonwebtoken": "8.2.0", - "lodash.set": "4.3.2" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - } - } - }, - "express-unless": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", - "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "2.0.0" - } - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" - }, - "fraction.js": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.9.tgz", - "integrity": "sha512-qP1sNwdrcA+Vs5TTvGETuaaUmz4Tm48V6Jc+8Oh/gqvkb1d42s99w5kvSrZkIATp/mz3rV4CTef6xINkCofu+A==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "requires": { - "is-property": "1.0.2" - } - }, - "generic-pool": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", - "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "graphql": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.1.tgz", - "integrity": "sha512-awNp3LTrQ7dJDSX3p3PBuxNDC+WFSOrWeV6+l4Xeh2PQJVOFyQ9SZPonXRz2WZc7aIxLZsf2nDZuuuc0qyEq/A==", - "requires": { - "iterall": "1.2.2" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-bluebird": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "iterall": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", - "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" - }, - "javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" - }, - "js-beautify": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", - "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", - "requires": { - "config-chain": "1.1.11", - "editorconfig": "0.13.3", - "mkdirp": "0.5.1", - "nopt": "3.0.6" - } - }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "jsonwebtoken": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", - "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", - "requires": { - "jws": "3.1.4", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1", - "xtend": "4.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "jwa": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", - "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", - "requires": { - "base64url": "2.0.0", - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.9", - "safe-buffer": "5.1.1" - } - }, - "jws": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", - "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", - "requires": { - "base64url": "2.0.0", - "jwa": "1.1.5", - "safe-buffer": "5.1.1" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "1.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", - "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", - "requires": { - "pseudomap": "1.0.2" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "requires": { - "es5-ext": "0.10.42" - } - }, - "mathjs": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-5.2.0.tgz", - "integrity": "sha512-TwiegJ/k9zXRhyYFjeuBB5Nmw+GXDq4pRC9bcRhRlq654DZLv4QAvp24l9dEk2G2QiU1PfzvP1dd/CCEqsMZ+w==", - "requires": { - "complex.js": "2.0.11", - "decimal.js": "10.0.1", - "escape-latex": "1.1.1", - "fraction.js": "4.0.9", - "javascript-natural-sort": "0.7.1", - "seed-random": "2.2.0", - "tiny-emitter": "2.0.2", - "typed-function": "1.1.0" - } - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "1.1.6" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "1.2.0" - } - }, - "memoizee": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", - "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-weak-map": "2.0.2", - "event-emitter": "0.3.5", - "is-promise": "2.1.0", - "lru-queue": "0.1.0", - "next-tick": "1.0.0", - "timers-ext": "0.1.5" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-graphql-schemas": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/merge-graphql-schemas/-/merge-graphql-schemas-1.5.1.tgz", - "integrity": "sha512-J5+FVjI7IaMXGNNLYotSGGbHmHb4Vd2KjxB45QsO5B6tSc81bJcilbtdx30fkZv7jIs7u0F8jcf7C0KfcSDBFA==", - "requires": { - "deepmerge": "2.1.0", - "glob": "7.1.2", - "is-glob": "4.0.0" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "1.33.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "moment": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", - "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" - }, - "moment-timezone": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", - "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", - "requires": { - "moment": "2.21.0" - } - }, - "mongodb": { - "version": "2.2.35", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.35.tgz", - "integrity": "sha512-3HGLucDg/8EeYMin3k+nFWChTA85hcYDCw1lPsWR6yV9A6RgKb24BkLiZ9ySZR+S0nfBjWoIUS7cyV6ceGx5Gg==", - "requires": { - "es6-promise": "3.2.1", - "mongodb-core": "2.1.19", - "readable-stream": "2.2.7" - } - }, - "mongodb-core": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.19.tgz", - "integrity": "sha512-Jt4AtWUkpuW03kRdYGxga4O65O1UHlFfvvInslEfLlGi+zDMxbBe3J2NVmN9qPJ957Mn6Iz0UpMtV80cmxCVxw==", - "requires": { - "bson": "1.0.6", - "require_optional": "1.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mysql2": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.6.4.tgz", - "integrity": "sha512-ZYbYgK06HKfxU45tYYLfwW5gKt8BslfE7FGyULNrf2K2fh+DuEX+e0QKsd2ObpZkMILefaVn8hsakVsTFqravQ==", - "requires": { - "denque": "1.3.0", - "generate-function": "2.3.1", - "iconv-lite": "0.4.24", - "long": "4.0.0", - "lru-cache": "4.1.3", - "named-placeholders": "1.1.1", - "seq-queue": "0.0.5", - "sqlstring": "2.3.1" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "named-placeholders": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.1.tgz", - "integrity": "sha1-O3oNJiA910s6nfTJz7gnsvuQfmQ=", - "requires": { - "lru-cache": "2.5.0" - }, - "dependencies": { - "lru-cache": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz", - "integrity": "sha1-2COIrpyWC+y+oMc7uet5tsbOmus=" - } - } - }, - "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1.1.1" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "4.3.2", - "validate-npm-package-license": "3.0.3" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "1.2.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "packet-reader": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", - "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "1.3.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "2.3.0" - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" - }, - "pg": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", - "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", - "requires": { - "buffer-writer": "1.0.1", - "js-string-escape": "1.0.1", - "packet-reader": "0.3.1", - "pg-connection-string": "0.1.3", - "pg-pool": "2.0.3", - "pg-types": "1.12.1", - "pgpass": "1.0.2", - "semver": "4.3.2" - } - }, - "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "pg-hstore": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.2.tgz", - "integrity": "sha1-9+8FPnubiSrphq8vfL6GQy388k8=", - "requires": { - "underscore": "1.8.3" - } - }, - "pg-pool": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", - "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" - }, - "pg-types": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", - "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", - "requires": { - "postgres-array": "1.0.2", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.3", - "postgres-interval": "1.1.1" - } - }, - "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", - "requires": { - "split": "1.0.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "postgres-array": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", - "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" - }, - "postgres-date": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", - "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" - }, - "postgres-interval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", - "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", - "requires": { - "xtend": "4.0.1" - } - }, - "printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "redis": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", - "requires": { - "double-ended-queue": "2.1.0-0", - "redis-commands": "1.3.5", - "redis-parser": "2.6.0" - } - }, - "redis-commands": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" - }, - "redis-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", - "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "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.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "resolve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.0.tgz", - "integrity": "sha512-QdgZ5bjR1WAlpLaO5yHepFvC+o3rCr6wpfE2tpJNMkXdulf2jKomQBdNRQITF3ZKHNlT71syG98yQP03gasgnA==", - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "retry-as-promised": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", - "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", - "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" - }, - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - } - }, - "seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" - }, - "sequelize": { - "version": "4.35.2", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.35.2.tgz", - "integrity": "sha512-uK1vpgq4OkRdUG1pEGfeCkudQaoDqfX/hNICm7tqsDYHjADEU/E/Bn9+ib2JQkKhIcX4X4Sp1/88/lsofsXPzA==", - "requires": { - "bluebird": "3.5.1", - "cls-bluebird": "2.1.0", - "debug": "3.1.0", - "depd": "1.1.2", - "dottie": "2.0.0", - "generic-pool": "3.4.2", - "inflection": "1.12.0", - "lodash": "4.17.5", - "moment": "2.21.0", - "moment-timezone": "0.5.14", - "retry-as-promised": "2.3.2", - "semver": "5.5.0", - "terraformer-wkt-parser": "1.1.2", - "toposort-class": "1.0.1", - "uuid": "3.2.1", - "validator": "9.4.1", - "wkx": "0.4.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "sequelize-cli": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-4.0.0.tgz", - "integrity": "sha1-TWQd+1iwNwq0QPc34bC/c38V7KU=", - "requires": { - "bluebird": "3.5.1", - "cli-color": "1.2.0", - "fs-extra": "5.0.0", - "js-beautify": "1.7.5", - "lodash": "4.17.5", - "resolve": "1.7.0", - "umzug": "2.1.0", - "yargs": "8.0.2" - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "sha-1": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sha-1/-/sha-1-0.1.1.tgz", - "integrity": "sha1-KjkwS/QburEd2e+3R07CWxqSwlc=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2.3.8" - } - }, - "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" - }, - "ssf": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.2.tgz", - "integrity": "sha512-rDhAPm9WyIsY8eZEKyE8Qsotb3j/wBdvMWBUsOhJdfhKGLfQidRjiBUV0y/MkyCLiXQ38FG6LWW/VYUtqlIDZQ==", - "requires": { - "frac": "1.1.2" - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "streamifier": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", - "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "superagent": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", - "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.2", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.2.7" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - }, - "supertest": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz", - "integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==", - "requires": { - "methods": "1.1.2", - "superagent": "3.8.2" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "terraformer": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", - "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", - "requires": { - "@types/geojson": "1.0.6" - } - }, - "terraformer-wkt-parser": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", - "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", - "requires": { - "terraformer": "1.0.8" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timers-ext": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.5.tgz", - "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", - "requires": { - "es5-ext": "0.10.42", - "next-tick": "1.0.0" - } - }, - "tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.18" - } - }, - "typed-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.0.tgz", - "integrity": "sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA==" - }, - "umzug": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz", - "integrity": "sha512-BgT+ekpItEWaG+3JjLLj6yVTxw2wIH8Cr6JyKYIzukWAx9nzGhC6BGHb/IRMjpobMM1qtIrReATwLUjKpU2iOQ==", - "requires": { - "babel-runtime": "6.26.0", - "bluebird": "3.5.1", - "lodash": "4.17.5", - "resolve": "1.7.0" - } - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "uuidv4": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-2.0.0.tgz", - "integrity": "sha512-sAUlwUVepcVk6bwnaW/oi6LCwMdueako5QQzRr90ioAVVcms6p1mV0PaSxK8gyAC4CRvKddsk217uUpZUbKd2Q==", - "requires": { - "sha-1": "0.1.1", - "uuid": "3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } - } - }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" - } - }, - "validator": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", - "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wkx": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.4.tgz", - "integrity": "sha512-eVVHka2jRaAp9QanKhLpxWs3AGDV0b8cijlavxBnn4ryXzq5N/3Xe3nkQsI0XMRA16RURwviCWuOCj4mXCmrxw==", - "requires": { - "@types/node": "9.4.6" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xlsx": { - "version": "0.12.11", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.12.11.tgz", - "integrity": "sha1-VIBteKe7ApmKuj1ZxJmVpvC+AB4=", - "requires": { - "adler-32": "1.2.0", - "cfb": "1.0.7", - "codepage": "1.12.2", - "commander": "2.14.1", - "crc-32": "1.2.0", - "exit-on-epipe": "1.0.1", - "ssf": "0.10.2" - }, - "dependencies": { - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - } - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "requires": { - "camelcase": "4.1.0" - } - } - } -} diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 8df9caf..b08c362 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -12,7 +12,11 @@ module.exports.ifHasValidatorFunctionInvoke = function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { - return dataModel.prototype[validatorFunction](data).error; + if(validatorFunction === 'validatorForDelete'){ + return dataModel.prototype[validatorFunction](dataModel); + }else{ + return dataModel.prototype[validatorFunction](data).error; + } } }; From f24c393d3c2a52d8085ac10190bc95dcf1e632ca Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Tue, 5 Mar 2019 16:21:17 -0600 Subject: [PATCH 050/125] joi validator policy for delete changed --- utils/validatorUtil.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index b08c362..8df9caf 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -12,11 +12,7 @@ module.exports.ifHasValidatorFunctionInvoke = function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { - if(validatorFunction === 'validatorForDelete'){ - return dataModel.prototype[validatorFunction](dataModel); - }else{ - return dataModel.prototype[validatorFunction](data).error; - } + return dataModel.prototype[validatorFunction](data).error; } }; From 2521cb1aeb214bcea81ae93f341c0d705de2115e Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Wed, 6 Mar 2019 10:54:24 -0600 Subject: [PATCH 051/125] Sequelize independent validation for CSV batch upload --- utils/file-tools.js | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index dc13ed6..4ed9260 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -2,8 +2,9 @@ const XLSX = require('xlsx'); const Promise = require('bluebird'); const promise_csv_parse = Promise.promisify(require('csv-parse')); const csv_parse = require('csv-parse'); -const fs = require('fs') -const awaitifyStream = require('awaitify-stream') +const fs = require('fs'); +const awaitifyStream = require('awaitify-stream'); +const validatorUtil = require('./validatorUtil'); /** @@ -77,8 +78,7 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { // Wrap all database actions within a transaction: let transaction = await model.sequelize.transaction(); try { - // Pipe a file read-stream through a CSV-Reader and make the records - // handleable asynchronously: + // Pipe a file read-stream through a CSV-Reader and handle records asynchronously: let csvStream = awaitifyStream.createReader( fs.createReadStream(csvFilePath).pipe( csv_parse({ @@ -86,24 +86,32 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { columns: cols }) ) - ) + ); let record; let errors = []; while (null !== (record = await csvStream.readAsync())) { - console.log(`Read record: ${JSON.stringify(record)}`); - await model.create(record, { - transaction: transaction + let error = validatorUtil.ifHasValidatorFunctionInvoke('validatorForCreate', model, record); - }).catch(error => { + if (!!error) { - console.log(`Caught error in while-loop: ${JSON.stringify(error)}`) - error.record = record; - errors.push(error); + console.log(`Validation error during CSV batch upload: ${JSON.stringify(error)}`); + error.record = record; + errors.push(error); - }) + } else { + + await model.create(record, { + transaction: transaction + }).catch(error => { + console.log(`Caught sequelize error during CSV batch upload: ${JSON.stringify(error)}`); + error.record = record; + errors.push(error); + }) + + } } if(errors.length > 0) { From 290dd1d4892a5baf4ace695ba3f72fe5524c8536 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Thu, 14 Mar 2019 09:31:28 +0100 Subject: [PATCH 052/125] Added Dockerfile.graphql_server - can be used by any User for their own environment and application - is also used by ScienceDbStarterPack. --- Dockerfile.graphql_server | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Dockerfile.graphql_server diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server new file mode 100644 index 0000000..42c30ed --- /dev/null +++ b/Dockerfile.graphql_server @@ -0,0 +1,14 @@ +FROM node:11.9.0-stretch-slim + +# Create app directory +WORKDIR /usr/src/app + +# Copy generated code into the skeleton GraphQL-Server +COPY . . + +# Clone the skeleton project and install dependencies +RUN apt-get update && apt-get install -y git procps &&\ + chmod u+x ./migrateDbAndStartServer.sh && \ + npm install + +EXPOSE 3000 From 998724a63840833a00038baafdb44cc9c4f6140f Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Tue, 19 Mar 2019 07:47:17 -0600 Subject: [PATCH 053/125] JOIN models --- package.json | 3 +- server.js | 38 +++++++-- utils/join-models.js | 186 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 utils/join-models.js diff --git a/package.json b/package.json index adee4c5..7cb7b68 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "sync-request": "^6.0.0", "nodemailer" : "^5.1.1", "nodemailer-smtp-transport": "^2.7.4", - "joi" : "^14.3.1" + "joi" : "^14.3.1", + "linked-list": "^2.0.0" }, "devDependencies": { "mocha": "^5.2.0", diff --git a/server.js b/server.js index 6525d53..1ec6d4e 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,7 @@ const fileUpload = require('express-fileupload'); const auth = require('./utils/login'); const bodyParser = require('body-parser'); + const Join = require('./utils/join-models'); var { buildSchema @@ -53,9 +54,9 @@ } /* Schema */ -console.log('Merging Schema') + console.log('Merging Schema'); var merged_schema = mergeSchema(path.join(__dirname, './schemas')); -console.log(merged_schema) + console.log(merged_schema); var Schema = buildSchema(merged_schema); /* Resolvers*/ @@ -72,11 +73,38 @@ app.use('/login', cors(), (req, res)=>{ auth.login(req.body).then( (token) =>{ res.json({token: token}); }).catch((err) =>{ - console.log(err) - res.status(500).send({error:"Wrong email or password. Please check your credentials."}) + console.log(err); + res.status(500).send({error: "Wrong email or password. Please check your credentials."}) }); -}) +}); + + + + +app.use('/join', cors(), (req, res) => { + + // check if the Content-Type is in JSON so that bodyParser can be applied automatically + if (!req.is('application/json')) + return res.status(415).send({error: "JSON Content-Type expected"}); + + Join.joinModels(req.body, res).then(() => { + console.log("joinModels success"); + res.end(); + }).catch(err => { + console.log(err); + res.status(500).send({error: err.message}); + }); +}); + + + + + + + + + app.use(fileUpload()); /*request is passed as context by default */ diff --git a/utils/join-models.js b/utils/join-models.js new file mode 100644 index 0000000..669ed78 --- /dev/null +++ b/utils/join-models.js @@ -0,0 +1,186 @@ + +// here I can list all existing local models with their names +const models = require('../models/index'); +const resolvers = require('../resolvers/index'); +const inflection = require('inflection'); +var LinkedList = require('linked-list'); + +// TODO: no index inside web-service models +//const webServiceModels = require('../models-webservice/index.js'); + +// modelAdjacencies is an array of JSON objects of the form: +// '[ { "name" : "A" } , { "name" : "B" } ]' + +// joinModels is called by express server directly. This function execution can take +// a long time so it should not be blocking and should not produce long call stack chains +module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) { + + if ( ! modelAdjacencies || modelAdjacencies.length === 0) + throw Error(`modelAdjacencies array is undefined`); + + // create a linked list from the input adjacency array + // this list will always keep the current model and the next model that is more useful than + // a plain array + let list = new LinkedList; + + for(let model_adj of modelAdjacencies) { + let item = new LinkedList.Item(); + item.model_adj = model_adj; + list.append(item); + } + + // iterate over the list and add some useful information to it's elements + let cur = list.head; + do{ + + // required on this step input data validation + if( ! cur.model_adj.name ) throw Error(`Model name is not defined in ${JSON.stringify(cur.model_adj)}`); + if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); + + // store raw names of the model attributes + let model = models[cur.model_adj.name]; + cur.model_adj.attributes = []; + for(let attribute_name of Object.keys(model.rawAttributes)) + cur.model_adj.attributes.push(attribute_name); + + // function that searches by criteria and offset instances of the given model_adj + cur.model_adj.func_findNext = defineFindNext(cur); + + // generic function that searches for this model by criteria + cur.model_adj.func_findThis = resolvers[inflection.pluralize(cur.model_adj.name)]; + + // not used before offset for the current model + cur.model_adj.offset = 0; + + // current data (at the moment of introspect - it's invalid, but at the moment of + // func_findNext - is valid) + cur.model_adj.data = null; + + }while(null !== (cur = cur.next)); + + + // print list elements + /*cur = list.head; + do{ + console.log(cur.model_adj); + }while(null !== (cur = cur.next));*/ + + + // http send stream header + let timestamp = new Date().getTime(); + httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', + 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + + while(true){ + /* + Function introspect will add "data" field to each adjacency + and will augment required offset for next call + + In the case that there is no data, this function will return "false" object. + */ + + try { + + list.head = await introspect(list.head); + + if (list.head.model_adj.data !== null) { + + // send complete joined data raw to the end-user + let row_string = constructRow(list.head); + await httpWritableStream.write(row_string); + + } else { + //TODO: redirect to success page from + break; + } + }catch(err){ + /* + We can't throw an error to Express at this stage because the response Content-Type + was already sent. So we can try to attach it to the end of file. + */ + console.log(err.message); + await httpWritableStream.write(`{error : ${err.message}`); + return; + } + } +}; + +defineFindNext = function (cur){ + + // get curr adjacency data and if not null - increment offset + // if id_ for the next adjacency is not null - return introspect + // if id_ for the next adjacency is null - do not increment anything and just return + // if we are on the first adjacency and the is a null result - just return false + // do not catch any errors - they will be cached automatically in the caller function + + // there is no findNext function for the list tail + if(cur.next === null) + return null; + + let cur_model = models[cur.model_adj.name]; + let next_model = models[cur.next.model_adj.name]; + + // cur.model_adj.func_search = resolvers[inflection.pluralize(cur.model_adj.name)]; + // model name that stores private keys of the given model_adj + // TODO: ... + + // returns modified model_adj_item (with offset moved, and data filled) + return function(cur){ + + console.log(`findNext invoked for ${cur.model_adj.name}`); + //TODO: cur.next.model_adj.data = ...(cur.data ... cur.next.offset)... + //TODO: cur.next.model_adj.offset++; + //if(...data != null... && cur.next.func_findNext != null) + // cur.next.next = cur.next.func_findNext(cur.next); + + console.log("PRINT ASSOCIATIONS"); + console.log(cur_model.getAssociations(next_model)[0].); + console.log("<<<<<<<<<<<<<<<<<<<"); + + return cur.next; + } +}; + +/* + to find next data I need to have a valid cur data + */ +introspect = async function (head){ + + let params = {}; + + params.pagination = { + offset : head.model_adj.offset, + limit : 1 + }; + + if( ! head.model_adj.search) + params.search = head.model_adj.search; + + if( ! head.model_adj.order) + params.order = head.model_adj.order; + + //TODO: Remove user session stub!!! + let context = { + acl : null + }; + + head.model_adj.data = await head.model_adj.func_findThis(params, context); + if( head.model_adj.data.length === 0 ) head.model_adj.data = null; + head.model_adj.offset++; + + //TODO: Remove this + //console.log("INTROSPECTION STARTED"); + //console.log(head.model_adj.data); + + // explore subsequent objects recursively + if(head.model_adj.data != null && head.model_adj.func_findNext != null) + head.next = head.model_adj.func_findNext(head); + + + + return head; +}; + +constructRow = function(model_adj_head){ + return `raw constructed`; +}; \ No newline at end of file From e294e37f1bd97cfd0cbaf0bcbae0146d6f848f98 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Tue, 19 Mar 2019 07:48:01 -0600 Subject: [PATCH 054/125] JOIN models --- utils/join-models.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/join-models.js b/utils/join-models.js index 669ed78..c9a9170 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -183,4 +183,6 @@ introspect = async function (head){ constructRow = function(model_adj_head){ return `raw constructed`; -}; \ No newline at end of file +}; + +// curl -d '[ { "name" : "individual", "cur_id" : 1 } , { "name" : "transcript_count" , "cur_id" : 2} ]' -H "Content-Type: application/json" http://localhost:3000/join \ No newline at end of file From 940812e19a5e42210319ab87d8698d93179329c5 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 20 Mar 2019 21:39:15 -0600 Subject: [PATCH 055/125] add environment variable PORT --- config/globals.js | 2 +- server.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config/globals.js b/config/globals.js index 96616dd..972cc41 100644 --- a/config/globals.js +++ b/config/globals.js @@ -1,6 +1,6 @@ module.exports = { LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, - + PORT : process.env.PORT || 3000, MAIL_SERVICE: "gmail", MAIL_HOST: "smtp.gmail.com", MAIL_ACCOUNT: "sci.db.service@gmail.com", diff --git a/server.js b/server.js index 6525d53..aaf6911 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,7 @@ const fileUpload = require('express-fileupload'); const auth = require('./utils/login'); const bodyParser = require('body-parser'); + const globals = require('./config/globals'); var { buildSchema @@ -16,13 +17,13 @@ /* Server */ - const APP_PORT = 3000; + const APP_PORT = globals.PORT; const app = express(); app.use((req, res, next)=> { // Website you wish to allow to connect - res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); + res.setHeader('Access-Control-Allow-Origin', globals.ALLOW_ORIGIN); //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); // Request methods you wish to allow From 35f23ec99e2ed92a84ecc6a3e4dc9c3be6d0346f Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 20 Mar 2019 21:56:49 -0600 Subject: [PATCH 056/125] allow-origin environment variable --- config/globals.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/globals.js b/config/globals.js index 972cc41..1139f7f 100644 --- a/config/globals.js +++ b/config/globals.js @@ -1,6 +1,7 @@ module.exports = { LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, PORT : process.env.PORT || 3000, + ALLOW_ORIGIN: process.env.ALLOW_ORIGIN || "http://localhost:8080", MAIL_SERVICE: "gmail", MAIL_HOST: "smtp.gmail.com", MAIL_ACCOUNT: "sci.db.service@gmail.com", From 5e0566c3faacab374c30ac686eb981d4d61a2e5d Mon Sep 17 00:00:00 2001 From: Veronica Suaste Date: Wed, 20 Mar 2019 22:07:24 -0600 Subject: [PATCH 057/125] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 23558c9..97ec8f8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ $ npm start ``` $ node_modules/.bin/sequelize db:migrate ``` command will create the tables specified in the ```migrations``` folder. With credential as in ``` config/config.json``` file. +### ENVIRONMENT VARIABLES + +* `PORT` - The port where the app is listening, default value is `3000` +* `ALLOW_ORIGIN` - In development mode we need to specify the header `Access-Control-Allow-Origin` so the SPA application can communicate with the server, default value `http://localhost:8080`. ## NOTE A data base should be already configured locally as in `config/config.json` From 315bd82edf826baad497c3daf5209dbc8774bcaf Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Thu, 21 Mar 2019 17:21:53 -0600 Subject: [PATCH 058/125] join refactoring one --- utils/join-models.js | 224 ++++++++++++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 75 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index c9a9170..a84a01d 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -43,23 +43,20 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) for(let attribute_name of Object.keys(model.rawAttributes)) cur.model_adj.attributes.push(attribute_name); - // function that searches by criteria and offset instances of the given model_adj - cur.model_adj.func_findNext = defineFindNext(cur); - - // generic function that searches for this model by criteria - cur.model_adj.func_findThis = resolvers[inflection.pluralize(cur.model_adj.name)]; + // current data (is initialized by the 'func_find' call) + cur.model_adj.data = null; - // not used before offset for the current model - cur.model_adj.offset = 0; + // in the case when SELECT query that is formed by func_find method can return + // a collection of database records, those can be filtered and ordered accordingly + // to "find_params" structure. This structure also includes "offset" initialized to 0 + cur.model_adj.search_params = defineSearchParams(cur); - // current data (at the moment of introspect - it's invalid, but at the moment of - // func_findNext - is valid) - cur.model_adj.data = null; + // function that searches by criteria and offset instances of the given model_adj + cur.model_adj.func_find = defineFindFunction(cur); }while(null !== (cur = cur.next)); - - // print list elements + //TODO: Kill this /*cur = list.head; do{ console.log(cur.model_adj); @@ -71,6 +68,11 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + //TODO: Remove user session stub!!! + let context = { + acl : null + }; + while(true){ /* Function introspect will add "data" field to each adjacency @@ -81,18 +83,30 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) try { - list.head = await introspect(list.head); + // iterate over the list of associated models + cur = list.head; + do{ + console.log(`Get data for: ${cur.model_adj.name}`); + cur = await cur.model_adj.func_find(cur, context); - if (list.head.model_adj.data !== null) { + // no data found for the cur element of association chain + if(cur.model_adj.data === null) + break; - // send complete joined data raw to the end-user - let row_string = constructRow(list.head); - await httpWritableStream.write(row_string); + }while(null !== (cur = cur.next)); + + console.log("out from introspection iterator"); + + // the end of the introspection is reached when there is no mode data in the head + // list element + if(cur === list.head && cur.model_adj.data === null) break; + + // send complete joined data raw to the end-user + // here the data line terminates with the first data == null and not + // necessary with the tail element + let row_string = constructRow(list.head); + await httpWritableStream.write(row_string); - } else { - //TODO: redirect to success page from - break; - } }catch(err){ /* We can't throw an error to Express at this stage because the response Content-Type @@ -105,84 +119,144 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) } }; -defineFindNext = function (cur){ +/* + Function use offset to retrieve corresponding data for the current list element according + to the current offset. This function will renew the cur.model_adj.data and augment + the cur.model_adj.offset field. If there is no data for the current offset, the + cur.model_adj.data will be set to null. + + It is assumed, that cur->prev element has already initialized it's data field. If cur->prev is null, it means that + we are working with the list head. If after calling this function, the cur.model_adj.data is null, + it means that there is nothing mode to do, and the JOIN process has successfully completed. +*/ + +defineFindFunction = function (cur){ + + + if(cur.prev === null){ - // get curr adjacency data and if not null - increment offset - // if id_ for the next adjacency is not null - return introspect - // if id_ for the next adjacency is null - do not increment anything and just return - // if we are on the first adjacency and the is a null result - just return false - // do not catch any errors - they will be cached automatically in the caller function + // cur is the head element of the list + return async function(cur, context){ - // there is no findNext function for the list tail - if(cur.next === null) - return null; + // for head getter function has to be estimated just once + if( ! cur.model_adj.func_getter) + cur.model_adj.func_getter = resolvers[inflection.pluralize(cur.model_adj.name)]; + + // database query + cur.model_adj.data = await cur.model_adj.func_getter(cur.model_adj.search_params, context); + + //TODO: not a nice check + if( cur.model_adj.data.length === 0 ) { + cur.model_adj.data = null; + }else{ + cur.model_adj.data = cur.model_adj.data[0]; + } + + // head offset never gets back to it's initial value of 0 + cur.model_adj.search_params.pagination.offset++; + + return cur; + } - let cur_model = models[cur.model_adj.name]; - let next_model = models[cur.next.model_adj.name]; + } else { - // cur.model_adj.func_search = resolvers[inflection.pluralize(cur.model_adj.name)]; - // model name that stores private keys of the given model_adj - // TODO: ... + let model_prev = models[cur.prev.model_adj.name]; - // returns modified model_adj_item (with offset moved, and data filled) - return function(cur){ + //********************DRY CODE***************************** + //TODO: Add association information inside a model generator + const nameAssocLc = cur.model_adj.name; + const nameAssocPl = inflection.pluralize(nameAssocLc); - console.log(`findNext invoked for ${cur.model_adj.name}`); - //TODO: cur.next.model_adj.data = ...(cur.data ... cur.next.offset)... - //TODO: cur.next.model_adj.offset++; - //if(...data != null... && cur.next.func_findNext != null) - // cur.next.next = cur.next.func_findNext(cur.next); + //TODO: Here are associoation name that sohuld not coincide with the target model name!!! + //<%- nameLc -%>.prototype.<%=associations_one[i].name%> + let func_toOneGetter = model_prev.prototype[nameAssocLc]; - console.log("PRINT ASSOCIATIONS"); - console.log(cur_model.getAssociations(next_model)[0].); - console.log("<<<<<<<<<<<<<<<<<<<"); + //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter --> associations_temp ??? + let func_toManyGetter = model_prev.prototype[`${nameAssocPl}Filter`]; + //********************^^^^^^^^^^***************************** + + + if(typeof func_toOneGetter === "function"){ + + // there is just one cur element can be found from the cur.prev that + // corresponds to the hasOne or belongsTo of the prev->cur association type + return async function(cur, context){ + console.log(`Invoking func_toOneGetter stub`); + return cur; + } + } else if(typeof func_toManyGetter === "function"){ + //TODO: Apply filters and ordering + // returns modified model_adj_item (with offset moved, and data filled) + return async function(cur, context){ + + // database query + cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); + + //TODO: not a nice check + if( cur.model_adj.data.length === 0 ){ + cur.model_adj.data = null; + cur.model_adj.search_params.pagination.offset = 0; + }else{ + cur.model_adj.search_params.pagination.offset++; + } + + return cur; + } + } else{ + //TODO: Ask if we are using always symmetrical associations? Add that to the documentation!!! + throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); + } + + } - return cur.next; - } }; -/* - to find next data I need to have a valid cur data - */ -introspect = async function (head){ - let params = {}; +defineSearchParams = function(cur){ + let search_params = {}; - params.pagination = { - offset : head.model_adj.offset, + search_params.pagination = { + offset : 0, limit : 1 }; - if( ! head.model_adj.search) - params.search = head.model_adj.search; + //TODO: Test filtering + if( ! cur.model_adj.search ) + search_params.search = cur.model_adj.search; - if( ! head.model_adj.order) - params.order = head.model_adj.order; + //TODO: Test ordering + if( ! cur.model_adj.order ) + search_params.order = cur.model_adj.order; - //TODO: Remove user session stub!!! - let context = { - acl : null - }; + return search_params; +}; - head.model_adj.data = await head.model_adj.func_findThis(params, context); - if( head.model_adj.data.length === 0 ) head.model_adj.data = null; - head.model_adj.offset++; - //TODO: Remove this - //console.log("INTROSPECTION STARTED"); - //console.log(head.model_adj.data); - // explore subsequent objects recursively - if(head.model_adj.data != null && head.model_adj.func_findNext != null) - head.next = head.model_adj.func_findNext(head); - return head; -}; -constructRow = function(model_adj_head){ - return `raw constructed`; +constructRow = function(head){ + + let str = ""; + + let cur = head; + do{ + str = str.concat(cur.model_adj.name); + + if(cur.next.model_adj.data === null) + break; + + if(cur.next !== null) + str = str.concat("->"); + + }while(null !== (cur = cur.next)); + + str = str.concat("\n"); + + return str; }; + // curl -d '[ { "name" : "individual", "cur_id" : 1 } , { "name" : "transcript_count" , "cur_id" : 2} ]' -H "Content-Type: application/json" http://localhost:3000/join \ No newline at end of file From 2ec665b9435e953c7a4cd971af7cb740f3b09a7a Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 22 Mar 2019 02:59:46 -0600 Subject: [PATCH 059/125] generic JOIN algorithm formuleted and passed first manual tests --- utils/join-models.js | 142 ++++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index a84a01d..6fe729a 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -56,12 +56,6 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) }while(null !== (cur = cur.next)); - //TODO: Kill this - /*cur = list.head; - do{ - console.log(cur.model_adj); - }while(null !== (cur = cur.next));*/ - // http send stream header let timestamp = new Date().getTime(); @@ -73,47 +67,76 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) acl : null }; + /* + This is the main introspection loop. On each iteration the user would receive + a new data raw. Accordingly to implementation of the constructRow function there + is a possibility to generate different output formats, hide unnecessary columns, etc. + This functionality is out of the scope of the current class and the constructRow + implementation has to be overloaded to output real data. See the child classes to get + more information. + */ while(true){ - /* - Function introspect will add "data" field to each adjacency - and will augment required offset for next call - In the case that there is no data, this function will return "false" object. - */ + // entering into the iterations from the head element + cur = list.head; try { - // iterate over the list of associated models - cur = list.head; - do{ - console.log(`Get data for: ${cur.model_adj.name}`); + while(true){ + let rollback = false; cur = await cur.model_adj.func_find(cur, context); - // no data found for the cur element of association chain - if(cur.model_adj.data === null) + // no data found for the cur element of association chain => print, rollback or exit + if(cur.model_adj.data === null){ + // cur element was visited for the first time: augment offset and print the line + if(cur.model_adj.search_params.pagination.offset === 0){ + cur = augmentOffsetFlushTrailing(cur); + break; + // cur element was visited before and has no data + }else{ + // head has no more data, terminate + if(cur.prev === null){ + return; + // cur has no data and was already printed + // goto prev, augment it's offset and try again + }else{ + cur = cur.prev; + cur = augmentOffsetFlushTrailing(cur); + rollback = true; + } + } + } + + // the last element was reached and it has data != null + if(cur.next === null){ + cur = augmentOffsetFlushTrailing(cur); break; + } - }while(null !== (cur = cur.next)); + // if it's not a rollback run - explore the next element + if(!rollback) + cur = cur.next; + } - console.log("out from introspection iterator"); + /* + Send joined data raw to the end-user accordingly to the constructRow implementation. - // the end of the introspection is reached when there is no mode data in the head - // list element - if(cur === list.head && cur.model_adj.data === null) break; + It should be stressed, that after the first element with data == null, all subsequent elements + have no valid data. Also, as long as offsets are used to check if a given element was + already visited (printed) or not, the offsets should not be modified within constructRow function, + and it's interpretation is not direct. + */ - // send complete joined data raw to the end-user - // here the data line terminates with the first data == null and not - // necessary with the tail element let row_string = constructRow(list.head); await httpWritableStream.write(row_string); }catch(err){ /* - We can't throw an error to Express at this stage because the response Content-Type + We can't throw an error to Express server at this stage because the response Content-Type was already sent. So we can try to attach it to the end of file. */ - console.log(err.message); - await httpWritableStream.write(`{error : ${err.message}`); + console.log(err); + await httpWritableStream.write(`{error : ${err.message}}\n`); return; } } @@ -142,19 +165,16 @@ defineFindFunction = function (cur){ if( ! cur.model_adj.func_getter) cur.model_adj.func_getter = resolvers[inflection.pluralize(cur.model_adj.name)]; - // database query + // get record from database for the given offset + // an output is an array that have one or zero elements cur.model_adj.data = await cur.model_adj.func_getter(cur.model_adj.search_params, context); - //TODO: not a nice check if( cur.model_adj.data.length === 0 ) { cur.model_adj.data = null; }else{ cur.model_adj.data = cur.model_adj.data[0]; } - // head offset never gets back to it's initial value of 0 - cur.model_adj.search_params.pagination.offset++; - return cur; } @@ -189,21 +209,24 @@ defineFindFunction = function (cur){ // returns modified model_adj_item (with offset moved, and data filled) return async function(cur, context){ - // database query + // get record from database for the given offset + // an output is an array that have one or zero elements cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); - //TODO: not a nice check if( cur.model_adj.data.length === 0 ){ cur.model_adj.data = null; - cur.model_adj.search_params.pagination.offset = 0; }else{ - cur.model_adj.search_params.pagination.offset++; + cur.model_adj.data = cur.model_adj.data[0]; } return cur; } } else{ - //TODO: Ask if we are using always symmetrical associations? Add that to the documentation!!! + /* + If you get this error, it means that there is no explicit link between cur.prev and cur elements. + For example, it is the case when + */ + throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); } @@ -211,7 +234,12 @@ defineFindFunction = function (cur){ }; - +/* + This helper function fills up a serach_params data structure. It's 'search' and 'order' + elements would never change during the given transmission session. However the pagination + parameter is important. The limit shell always be 1, and the offset is internal parameter of + the current algorithm. It is prohibited to alter offset values from the outside world. + */ defineSearchParams = function(cur){ let search_params = {}; @@ -231,11 +259,40 @@ defineSearchParams = function(cur){ return search_params; }; +/* + This helper function is used to augment offset of the "cur" element. + In this case offsets and data of the all trailing elements became invalid + and shell be flushed. + */ +augmentOffsetFlushTrailing = function(cur){ + cur.model_adj.search_params.pagination.offset++; + let next = cur.next; + while(next !== null){ + next.model_adj.search_params.pagination.offset = 0; + next.model_adj.data = null; + next = next.next; + } + return cur; +}; +/* + The basic implementation of the constructRow function that prints model names and id's + of the found elements. It is used for testing. + + ... + individual[id:458] ->transcript_count[id:6] + individual[id:459] ->transcript_count[id:2] + individual[id:460] + individual[id:461] + individual[id:462] ->transcript_count[id:7] + individual[id:462] ->transcript_count[id:10] + individual[id:463] ->transcript_count[id:8] + ... + */ constructRow = function(head){ @@ -243,9 +300,10 @@ constructRow = function(head){ let cur = head; do{ - str = str.concat(cur.model_adj.name); + str = str.concat(`${cur.model_adj.name}[`); + str = str.concat(`id:${cur.model_adj.data.id}] `); - if(cur.next.model_adj.data === null) + if(cur.next !== null && cur.next.model_adj.data === null) break; if(cur.next !== null) @@ -259,4 +317,4 @@ constructRow = function(head){ }; -// curl -d '[ { "name" : "individual", "cur_id" : 1 } , { "name" : "transcript_count" , "cur_id" : 2} ]' -H "Content-Type: application/json" http://localhost:3000/join \ No newline at end of file +// curl -d '[ { "name" : "individual"} , { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join \ No newline at end of file From 6c1958fc76b11e5acdd1df444a4b7aa8827cf093 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 22 Mar 2019 03:03:30 -0600 Subject: [PATCH 060/125] generic JOIN algorithm formuleted and passed first manual tests --- utils/join-models.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/join-models.js b/utils/join-models.js index 6fe729a..3fda44f 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -84,19 +84,25 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) while(true){ let rollback = false; + + // query the database (see defineFindFunction for details) cur = await cur.model_adj.func_find(cur, context); // no data found for the cur element of association chain => print, rollback or exit if(cur.model_adj.data === null){ + // cur element was visited for the first time: augment offset and print the line if(cur.model_adj.search_params.pagination.offset === 0){ cur = augmentOffsetFlushTrailing(cur); break; + // cur element was visited before and has no data }else{ + // head has no more data, terminate if(cur.prev === null){ return; + // cur has no data and was already printed // goto prev, augment it's offset and try again }else{ From 97747d6c041b3c66289f1b3e77a826c4adb578c4 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 22 Mar 2019 06:04:02 -0600 Subject: [PATCH 061/125] docs added at module header --- utils/join-models.js | 132 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 25 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 3fda44f..9257018 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -1,5 +1,76 @@ -// here I can list all existing local models with their names +/* + +MODULE INPUT DESCRIPTION + +The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. +Below goes an example of the currently supported parameter set: + +[ { + "name" : "individual", // Name of the model as it appears in the corresponding index.js + + "assoc" : { // (REQUIRED UNTIL...) An "assoc" structure describe how the model + // "individual" is associated with the model "transcript_count". This structure + // is required until the corresponding data will appear in the + // '../models/individual.js' file in future codegen releases. + // After that an "assoc" structure will not be required on the input. + + "as_name" : "transcript_counts", // (REQUIRED) There can be more than one association between two models, + // the way to differ between these associations is an "as_name" + // used by sequelize. This name is used by codegen to create resolvers and + // is used here to find them. + + "storage_type" // (REQUIRED - not implemented) This parameter is used to identify which + // index.js is to be used to find the associated model. + // If it is "web" - the '../models_webservice/index.js' will be used. + // If it is "sql" => the '../models/index.js'. + }, + + "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility + // to filter out unnecessary columns of the table. However, is is easy + // to implement this functionality inside a "constructRow" function. This way + // it can be possible to create different cut-offs of the database at the + // presentation level and resolve the data analysis problem at a low cost. + "name", + "createdAt" + ], + + <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the + // JOIN chain. + + + }, + { + "name" : "transcript_count", + // The last element of the association chain does not require an "assoc" + // structure, it has no sense here and will be ignored if present. + + "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or + // "belongsToMany", there can be more than one "transcript_count" record + "field" : "name", // associated with the same "individual". + "value" : { // The "transcript_count" records can be filtered and ordered + "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of + }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and + "operator" : "like" // "order" parameters will be ignored. + }, + + order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. + } +] + +********************************************************** + +CURL tests (copy-paste to console): + +curl -d '[ { "name" : "individual", "assoc" : {"as_name" : "transcript_counts", "as_type" : "hasMany"} }, { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join + + +curl -d '[ { "name" : "transcript_count", "assoc" : {"as_name" : "individual", "as_type" : "belongsTo"} }, { "name" : "individual"} ]' -H "Content-Type: application/json" http://localhost:3000/join + +*/ + +// TODO: Refactor to JS class + const models = require('../models/index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); @@ -8,9 +79,6 @@ var LinkedList = require('linked-list'); // TODO: no index inside web-service models //const webServiceModels = require('../models-webservice/index.js'); -// modelAdjacencies is an array of JSON objects of the form: -// '[ { "name" : "A" } , { "name" : "B" } ]' - // joinModels is called by express server directly. This function execution can take // a long time so it should not be blocking and should not produce long call stack chains module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) { @@ -62,7 +130,7 @@ module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', 'Content-disposition': `attachment; filename = ${timestamp}.json`}); - //TODO: Remove user session stub!!! + //TODO: Remove user session stub (this would require login session on the client side) let context = { acl : null }; @@ -186,39 +254,46 @@ defineFindFunction = function (cur){ } else { + /* + Here an explicit check is applied to detect for the association getter function in the cur.prev data model. + At the same time this is a validator (see the "else" option). + */ + let model_prev = models[cur.prev.model_adj.name]; - //********************DRY CODE***************************** - //TODO: Add association information inside a model generator - const nameAssocLc = cur.model_adj.name; - const nameAssocPl = inflection.pluralize(nameAssocLc); + const as_name = cur.prev.model_adj.assoc.as_name; + if( ! as_name ) throw Error('"assoc" structure is required, see the docs'); - //TODO: Here are associoation name that sohuld not coincide with the target model name!!! //<%- nameLc -%>.prototype.<%=associations_one[i].name%> - let func_toOneGetter = model_prev.prototype[nameAssocLc]; - - //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter --> associations_temp ??? - let func_toManyGetter = model_prev.prototype[`${nameAssocPl}Filter`]; - //********************^^^^^^^^^^***************************** + let func_toOneGetter = model_prev.prototype[as_name]; + //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter + let func_toManyGetter = model_prev.prototype[`${as_name}Filter`]; if(typeof func_toOneGetter === "function"){ // there is just one cur element can be found from the cur.prev that // corresponds to the hasOne or belongsTo of the prev->cur association type return async function(cur, context){ - console.log(`Invoking func_toOneGetter stub`); + const as_name = cur.prev.model_adj.assoc.as_name; + + if(cur.model_adj.search_params.pagination.offset > 0){ + cur.model_adj.data = null; + }else{ + cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); + } + return cur; } } else if(typeof func_toManyGetter === "function"){ - //TODO: Apply filters and ordering - // returns modified model_adj_item (with offset moved, and data filled) + return async function(cur, context){ - // get record from database for the given offset - // an output is an array that have one or zero elements + // get record from database for the current offset (it comes inside cur.model_adj.search_params data structure) + // an output is an array that would have one (because limit is always 1) or zero elements (if nothing was found) cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); + // set data to null explicitly or remove an array wrapper (anyway there is just one element) if( cur.model_adj.data.length === 0 ){ cur.model_adj.data = null; }else{ @@ -230,7 +305,10 @@ defineFindFunction = function (cur){ } else{ /* If you get this error, it means that there is no explicit link between cur.prev and cur elements. - For example, it is the case when + For example, assume that model A belongsTo model B. However, the madel B does not have a corresponding + hasMany or hasOne association with A. If you try to make a JOIN in the order B -> A, you will get + this "No association" exception. However, if you JOIN these models in the order A -> B, the corresponding + association resolver will be found. */ throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); @@ -240,6 +318,10 @@ defineFindFunction = function (cur){ }; + + + + /* This helper function fills up a serach_params data structure. It's 'search' and 'order' elements would never change during the given transmission session. However the pagination @@ -265,6 +347,9 @@ defineSearchParams = function(cur){ return search_params; }; + + + /* This helper function is used to augment offset of the "cur" element. In this case offsets and data of the all trailing elements became invalid @@ -320,7 +405,4 @@ constructRow = function(head){ str = str.concat("\n"); return str; -}; - - -// curl -d '[ { "name" : "individual"} , { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join \ No newline at end of file +}; \ No newline at end of file From aa7e8046206f10d9be3b3658fef9c96bb082a805 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 22 Mar 2019 08:18:46 -0600 Subject: [PATCH 062/125] docs added at module header --- utils/join-models.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 9257018..7d0c839 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -9,11 +9,10 @@ Below goes an example of the currently supported parameter set: [ { "name" : "individual", // Name of the model as it appears in the corresponding index.js - "assoc" : { // (REQUIRED UNTIL...) An "assoc" structure describe how the model + "assoc" : { // (REQUIRED) An "assoc" structure describe how the model // "individual" is associated with the model "transcript_count". This structure // is required until the corresponding data will appear in the // '../models/individual.js' file in future codegen releases. - // After that an "assoc" structure will not be required on the input. "as_name" : "transcript_counts", // (REQUIRED) There can be more than one association between two models, // the way to differ between these associations is an "as_name" From fb0f46877262cef92a68a71b4f1c1d2f4e2944ac Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 22 Mar 2019 10:20:32 -0600 Subject: [PATCH 063/125] added TODOs until issue close --- utils/join-models.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utils/join-models.js b/utils/join-models.js index 7d0c839..d151ea2 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -1,6 +1,13 @@ /* +// TODO: change assoc +// TODO: extend class and use slash +// TODO: add docks in standard format +// TODO: document all in off. docs +// TODO: close issue with single index and use it +// TODO: write integration tests + MODULE INPUT DESCRIPTION The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. From f91489646edf276e2f3b72b8494b4390966bdf36 Mon Sep 17 00:00:00 2001 From: Asis Hallab Date: Mon, 25 Mar 2019 13:27:10 +0100 Subject: [PATCH 064/125] Removed no longer needed installation of git apt-get install -y git procps is no longer required --- Dockerfile.graphql_server | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server index 42c30ed..8e8f441 100644 --- a/Dockerfile.graphql_server +++ b/Dockerfile.graphql_server @@ -7,8 +7,7 @@ WORKDIR /usr/src/app COPY . . # Clone the skeleton project and install dependencies -RUN apt-get update && apt-get install -y git procps &&\ - chmod u+x ./migrateDbAndStartServer.sh && \ +RUN chmod u+x ./migrateDbAndStartServer.sh && \ npm install EXPOSE 3000 From a7e9b1ddb399536f394f0087803dfa711f9efacf Mon Sep 17 00:00:00 2001 From: Asis Hallab Date: Mon, 25 Mar 2019 13:27:57 +0100 Subject: [PATCH 065/125] Switched to latest node:11.12.0-stretch-slim --- Dockerfile.graphql_server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server index 8e8f441..9eaf014 100644 --- a/Dockerfile.graphql_server +++ b/Dockerfile.graphql_server @@ -1,4 +1,4 @@ -FROM node:11.9.0-stretch-slim +FROM node:11.12.0-stretch-slim # Create app directory WORKDIR /usr/src/app From 938bc71f7b4271f4e53f3ad78834277251bbc7cb Mon Sep 17 00:00:00 2001 From: Asis Hallab Date: Mon, 25 Mar 2019 13:33:53 +0100 Subject: [PATCH 066/125] Removed '.git*' from '/usr/src/app' Due to the COPY command the local '.git*' files and dirs are also copied. This can cause problems when running 'npm install', especially when npm needs to use git to fetch packages and the `graphql-server` is a git submodule. As only the submodule is copied into the Docker image, but not the containing parent git project, git is confused and exits with an error. --- Dockerfile.graphql_server | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server index 9eaf014..83552d6 100644 --- a/Dockerfile.graphql_server +++ b/Dockerfile.graphql_server @@ -8,6 +8,7 @@ COPY . . # Clone the skeleton project and install dependencies RUN chmod u+x ./migrateDbAndStartServer.sh && \ + rm .git* && \ npm install EXPOSE 3000 From a5af41a66a97bfcdac5532e5c3301a69e3290bf2 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 25 Mar 2019 14:05:51 +0100 Subject: [PATCH 067/125] Added a separate Sequelize config for the Docker setup When using Postgres through Docker the host is 'postgres' and not 127.0.0.1 Created a new ./config/config_postgres_docker.json in which host is correctly set. In Dockerfile.graphql_server the above Sequelize config is moved to the expected location: mv ./config/config_postgres_docker.json ./config/config.json --- Dockerfile.graphql_server | 1 + config/config_postgres_docker.json | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 config/config_postgres_docker.json diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server index 83552d6..a146b73 100644 --- a/Dockerfile.graphql_server +++ b/Dockerfile.graphql_server @@ -9,6 +9,7 @@ COPY . . # Clone the skeleton project and install dependencies RUN chmod u+x ./migrateDbAndStartServer.sh && \ rm .git* && \ + mv ./config/config_postgres_docker.json ./config/config.json && \ npm install EXPOSE 3000 diff --git a/config/config_postgres_docker.json b/config/config_postgres_docker.json new file mode 100644 index 0000000..aa6cd69 --- /dev/null +++ b/config/config_postgres_docker.json @@ -0,0 +1,26 @@ +{ + "development": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_development", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "test": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_test", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "production": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_production", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false + } +} From 17989f1b63fd1e514e1fe237255de636558aaacb Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 25 Mar 2019 14:16:35 +0100 Subject: [PATCH 068/125] Corrected postgres hostname to the one used in ScienceDbStarterPack --- config/config_postgres_docker.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config_postgres_docker.json b/config/config_postgres_docker.json index aa6cd69..d120ba8 100644 --- a/config/config_postgres_docker.json +++ b/config/config_postgres_docker.json @@ -3,7 +3,7 @@ "username": "sciencedb", "password": "sciencedb", "database": "sciencedb_development", - "host": "postgres", + "host": "sdb_postgres", "dialect": "postgres", "operatorsAliases": false }, @@ -11,7 +11,7 @@ "username": "sciencedb", "password": "sciencedb", "database": "sciencedb_test", - "host": "postgres", + "host": "sdb_postgres", "dialect": "postgres", "operatorsAliases": false }, @@ -19,7 +19,7 @@ "username": "sciencedb", "password": "sciencedb", "database": "sciencedb_production", - "host": "postgres", + "host": "sdb_postgres", "dialect": "postgres", "operatorsAliases": false } From dc31509c03bd94cb989e963f86213884c91b4214 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 25 Mar 2019 20:16:22 +0100 Subject: [PATCH 069/125] Added executable permission to migrateDbAndStartServer.sh --- migrateDbAndStartServer.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 migrateDbAndStartServer.sh diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh old mode 100644 new mode 100755 From 726fb61d5089c6b8d144b47c3178efd44e8524f9 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 26 Mar 2019 14:19:01 +0100 Subject: [PATCH 070/125] Server managed with Docker will now check for seeders and run them, if not already done. --- migrateDbAndStartServer.sh | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh index d0d05bd..1ccc6cf 100755 --- a/migrateDbAndStartServer.sh +++ b/migrateDbAndStartServer.sh @@ -4,19 +4,27 @@ waited=0 until node ./utils/testSequelizeDbServerAvailable.js do - if [ $waited == 240 ]; then - echo -e '\nERROR: Time out reached while waiting for relational database server to be available.\n' - exit 1 - fi - sleep 2 - waited=$(expr $waited + 2) + if [ $waited == 240 ]; then + echo -e '\nERROR: Time out reached while waiting for relational database server to be available.\n' + exit 1 + fi + sleep 2 + waited=$(expr $waited + 2) done # Run the migrations if ! ./node_modules/.bin/sequelize db:migrate; then - echo -e '\nERROR: Migrating the relational database(s) caused an error.\n' - exit 1 + echo -e '\nERROR: Migrating the relational database(s) caused an error.\n' + exit 1 +fi + +# Run seeders if needed +if [ -d ./seeders ]; then + if ! ./node_modules/.bin/sequelize db:seed:all; then + echo -e '\nERROR: Seeding the relational database(s) caused an error.\n' + exit 1 + fi fi # Start GraphQL-server -npm start +npm start # acl From 7955d06eb0488aa168d321481f95a8ede762b85d Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 26 Mar 2019 14:32:30 +0100 Subject: [PATCH 071/125] Using a different dir in Docker graphql-server is now mounted into /usr/ScienceDbStarterPack/graphql-server This is to enable make npm install work in the context of ScienceDbStarterPack. The problem occurred when graphql-server is managed as a git submodule and npm install invokes git to fetch some remote npm packages. Git exits with an error, if the parent project including graphql-server as a submodule is not present. Tried many solutions. The working one is to mount actually the whole project and thus satisfy git's requirements. --- Dockerfile.graphql_server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server index a146b73..ac4fff3 100644 --- a/Dockerfile.graphql_server +++ b/Dockerfile.graphql_server @@ -1,7 +1,7 @@ FROM node:11.12.0-stretch-slim # Create app directory -WORKDIR /usr/src/app +WORKDIR /usr/ScienceDbStarterPack/graphql-server # Copy generated code into the skeleton GraphQL-Server COPY . . From 339d14d3a0d02c39ca8007bad4e34dd4c36b0ffa Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Thu, 28 Mar 2019 06:18:57 -0600 Subject: [PATCH 072/125] Closes: #10 --- models_index.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 models_index.js diff --git a/models_index.js b/models_index.js new file mode 100644 index 0000000..0a8d152 --- /dev/null +++ b/models_index.js @@ -0,0 +1,65 @@ +const fs = require('fs'); +const path = require('path') +sequelize = require('./connection'); + +var models = {}; + + +// ********************************************************************************** +// IMPORT SEQUEILIZE MODELS + +//grabs all the models in your models folder, adds them to the models object +fs.readdirSync("./models") + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + let model = sequelize['import'](path.join("./models", file)); + + + let validator_patch = path.join('./validations', file); + if(fs.existsSync(validator_patch)){ + model = require(`./${validator_patch}`).validator_patch(model); + } + + let patches_patch = path.join('./patches', file); + if(fs.existsSync(patches_patch)){ + model = require(`./${patches_patch}`).logic_patch(model); + } + + if(models[model.name]) + throw Error(`Duplicated model name ${model.name}`); + + models[model.name] = model; + + }); +//Important: creates associations based on associations defined in associate function in the model files +Object.keys(models).forEach(function(modelName) { + if (models[modelName].associate) { + models[modelName].associate(models); + } +}); +//update tables with association (temporary, just for testing purposes) +//this part is suppose to be done in the migration file +//sequelize.sync({force: true}); + + +// ********************************************************************************** +// IMPORT WEBSERVICES + +fs.readdirSync("./models-webservice") + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + let model = require(`./${path.join("./models-webservice", file)}`); + + if(models[model.name]) + throw Error(`Duplicated model name ${model.name}`); + + models[model.name] = model; + }); + +module.exports = models; + + From ddc09bd98a46dc1cbc5cf4a0e8df03bd06ee6a72 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Thu, 28 Mar 2019 14:31:13 -0600 Subject: [PATCH 073/125] JSON, CSV, Classes added --- server.js | 7 +- utils/join-models.js | 769 ++++++++++++++++++++++++++----------------- 2 files changed, 464 insertions(+), 312 deletions(-) diff --git a/server.js b/server.js index 1ec6d4e..20f0c85 100644 --- a/server.js +++ b/server.js @@ -5,7 +5,7 @@ const fileUpload = require('express-fileupload'); const auth = require('./utils/login'); const bodyParser = require('body-parser'); - const Join = require('./utils/join-models'); + const JOIN = require('./utils/join-models'); var { buildSchema @@ -88,8 +88,9 @@ app.use('/join', cors(), (req, res) => { if (!req.is('application/json')) return res.status(415).send({error: "JSON Content-Type expected"}); - Join.joinModels(req.body, res).then(() => { - console.log("joinModels success"); + let joinModels = new JOIN.JoinModels(); + + joinModels.run(req.body, res).then(() => { res.end(); }).catch(err => { console.log(err); diff --git a/utils/join-models.js b/utils/join-models.js index d151ea2..e11bf14 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -1,414 +1,565 @@ -/* -// TODO: change assoc -// TODO: extend class and use slash -// TODO: add docks in standard format -// TODO: document all in off. docs -// TODO: close issue with single index and use it -// TODO: write integration tests - -MODULE INPUT DESCRIPTION - -The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. -Below goes an example of the currently supported parameter set: - -[ { - "name" : "individual", // Name of the model as it appears in the corresponding index.js - - "assoc" : { // (REQUIRED) An "assoc" structure describe how the model - // "individual" is associated with the model "transcript_count". This structure - // is required until the corresponding data will appear in the - // '../models/individual.js' file in future codegen releases. - - "as_name" : "transcript_counts", // (REQUIRED) There can be more than one association between two models, - // the way to differ between these associations is an "as_name" - // used by sequelize. This name is used by codegen to create resolvers and - // is used here to find them. - - "storage_type" // (REQUIRED - not implemented) This parameter is used to identify which - // index.js is to be used to find the associated model. - // If it is "web" - the '../models_webservice/index.js' will be used. - // If it is "sql" => the '../models/index.js'. - }, - - "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility - // to filter out unnecessary columns of the table. However, is is easy - // to implement this functionality inside a "constructRow" function. This way - // it can be possible to create different cut-offs of the database at the - // presentation level and resolve the data analysis problem at a low cost. - "name", - "createdAt" - ], - - <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the - // JOIN chain. - - - }, - { - "name" : "transcript_count", - // The last element of the association chain does not require an "assoc" - // structure, it has no sense here and will be ignored if present. - - "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or - // "belongsToMany", there can be more than one "transcript_count" record - "field" : "name", // associated with the same "individual". - "value" : { // The "transcript_count" records can be filtered and ordered - "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of - }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and - "operator" : "like" // "order" parameters will be ignored. - }, - - order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. - } -] - -********************************************************** - -CURL tests (copy-paste to console): - -curl -d '[ { "name" : "individual", "assoc" : {"as_name" : "transcript_counts", "as_type" : "hasMany"} }, { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join +// TODO: document all in off. docs -curl -d '[ { "name" : "transcript_count", "assoc" : {"as_name" : "individual", "as_type" : "belongsTo"} }, { "name" : "individual"} ]' -H "Content-Type: application/json" http://localhost:3000/join - -*/ - -// TODO: Refactor to JS class +const _ = require('lodash'); +// TODO: Use generic index const models = require('../models/index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); -var LinkedList = require('linked-list'); - -// TODO: no index inside web-service models -//const webServiceModels = require('../models-webservice/index.js'); +let LinkedList = require('linked-list'); + + + +/** + * + * INPUT FORMAT DESCRIPTION + * + * The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. + * Below goes an example of the currently supported parameter set: + * + * [ { + * "name" : "individual", // Name of the model as it appears in the corresponding index.js + * + * + * "association_name" : "transcript_counts", + * // (REQUIRED) There can be more than one association between two models, + * // the way to differ between these associations is an "as_name" + * // used by sequelize. This name is used by codegen to create resolvers and + * // is used here to find them. + * + * + * + * "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility + * // to filter out unnecessary columns of the table. However, is is easy + * // to implement this functionality inside a "constructRow" function. This way + * // it can be possible to create different cut-offs of the database at the + * // presentation level and resolve the data analysis problem at a low cost. + * "name", + * "createdAt" + * ], + * + * <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the + * // JOIN chain. + * + * + * }, + * { + * "name" : "transcript_count", + * // The last element of the association chain does not require an "assoc" + * // structure, it has no sense here and will be ignored if present. + * + * "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or + * // "belongsToMany", there can be more than one "transcript_count" record + * "field" : "name", // associated with the same "individual". + * "value" : { // The "transcript_count" records can be filtered and ordered + * "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of + * }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and + * "operator" : "like" // "order" parameters will be ignored. + * }, + * + * order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. + * } + * ] + * + */ -// joinModels is called by express server directly. This function execution can take -// a long time so it should not be blocking and should not produce long call stack chains -module.exports.joinModels = async function(modelAdjacencies, httpWritableStream) { +class JoinModels { - if ( ! modelAdjacencies || modelAdjacencies.length === 0) - throw Error(`modelAdjacencies array is undefined`); + /** + * Internal class parameters + */ + constructor(){ - // create a linked list from the input adjacency array - // this list will always keep the current model and the next model that is more useful than - // a plain array - let list = new LinkedList; + // a linked list to be initialized from the input adjacency array + // this list will always keep the current model and the next model that is more useful than + // a plain array + this.list = new LinkedList; - for(let model_adj of modelAdjacencies) { - let item = new LinkedList.Item(); - item.model_adj = model_adj; - list.append(item); + // request context + //TODO: Remove user session stub (this would require login session on the client side) + this.context = { acl : null }; } - // iterate over the list and add some useful information to it's elements - let cur = list.head; - do{ + /** + * joinModels - is a function called by express server directly. This function execution can take + * a long time so it should not be blocking and should not produce long call stack chains - // required on this step input data validation - if( ! cur.model_adj.name ) throw Error(`Model name is not defined in ${JSON.stringify(cur.model_adj)}`); - if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); + * @param modelAdjacencies a number of parameters in JSON format that define a JOIN request (see below) + * @param httpWritableStream a writable http stream + * @returns {Promise} generally a void function + */ - // store raw names of the model attributes - let model = models[cur.model_adj.name]; - cur.model_adj.attributes = []; - for(let attribute_name of Object.keys(model.rawAttributes)) - cur.model_adj.attributes.push(attribute_name); + async run(modelAdjacencies, httpWritableStream) { - // current data (is initialized by the 'func_find' call) - cur.model_adj.data = null; + if ( ! modelAdjacencies || modelAdjacencies.length === 0) + throw Error(`modelAdjacencies array is undefined`); - // in the case when SELECT query that is formed by func_find method can return - // a collection of database records, those can be filtered and ordered accordingly - // to "find_params" structure. This structure also includes "offset" initialized to 0 - cur.model_adj.search_params = defineSearchParams(cur); + // a linked list to be initialized from the input adjacency array + // this list will always keep the current model and the next model that is more useful than + // a plain array + //let list = new LinkedList; - // function that searches by criteria and offset instances of the given model_adj - cur.model_adj.func_find = defineFindFunction(cur); + for(let model_adj of modelAdjacencies) { + let item = new LinkedList.Item(); + item.model_adj = model_adj; + this.list.append(item); + } - }while(null !== (cur = cur.next)); + // iterate over the list and add some useful information to it's elements + let cur = this.list.head; + do{ + // required on this step input data validation + if( ! cur.model_adj.name ) throw Error(`Model name is not defined in ${JSON.stringify(cur.model_adj)}`); + if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); - // http send stream header - let timestamp = new Date().getTime(); - httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', - 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + // store raw names of the model attributes if not given at input + let model = models[cur.model_adj.name]; + if( ! cur.model_adj.attributes ) { + cur.model_adj.attributes = []; + for (let attribute_name of Object.keys(model.rawAttributes)) + cur.model_adj.attributes.push(attribute_name); + }else{ + // check that specified attributes are correct + for (let attribute_name of cur.model_adj.attributes) + if( ! model.rawAttributes[attribute_name]) + throw Error(`Requested attribute '${attribute_name}' is not defined in the model '${cur.model_adj.name}'`); + } - //TODO: Remove user session stub (this would require login session on the client side) - let context = { - acl : null - }; + // current data (is initialized by the 'func_find' call) + cur.model_adj.data = null; - /* - This is the main introspection loop. On each iteration the user would receive - a new data raw. Accordingly to implementation of the constructRow function there - is a possibility to generate different output formats, hide unnecessary columns, etc. - This functionality is out of the scope of the current class and the constructRow - implementation has to be overloaded to output real data. See the child classes to get - more information. - */ - while(true){ + // in the case when SELECT query that is formed by func_find method can return + // a collection of database records, those can be filtered and ordered accordingly + // to "find_params" structure. This structure also includes "offset" initialized to 0 + cur.model_adj.search_params = this.defineSearchParams(cur); - // entering into the iterations from the head element - cur = list.head; + // function that searches by criteria and offset instances of the given model_adj + cur.model_adj.func_find = this.defineFindFunction(cur); - try { + }while(null !== (cur = cur.next)); - while(true){ - let rollback = false; - // query the database (see defineFindFunction for details) - cur = await cur.model_adj.func_find(cur, context); + // http send stream header + let timestamp = new Date().getTime(); + httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', + 'Content-disposition': `attachment; filename = ${timestamp}.json`}); - // no data found for the cur element of association chain => print, rollback or exit - if(cur.model_adj.data === null){ - // cur element was visited for the first time: augment offset and print the line - if(cur.model_adj.search_params.pagination.offset === 0){ - cur = augmentOffsetFlushTrailing(cur); - break; + /* + This is the main introspection loop. On each iteration the user would receive + a new data raw. Accordingly to implementation of the constructRow function there + is a possibility to generate different output formats, hide unnecessary columns, etc. + This functionality is out of the scope of the current class and the constructRow + implementation has to be overloaded to output real data. See the child classes to get + more information. + */ + while(true){ - // cur element was visited before and has no data - }else{ + // entering into the iterations from the head element + cur = this.list.head; + + try { + + while(true){ + let rollback = false; + + // query the database (see defineFindFunction for details) + cur = await cur.model_adj.func_find(cur, this.context); + + // no data found for the cur element of association chain => print, rollback or exit + if(cur.model_adj.data === null){ - // head has no more data, terminate - if(cur.prev === null){ - return; + // cur element was visited for the first time: augment offset and print the line + if(cur.model_adj.search_params.pagination.offset === 0){ + cur = this.augmentOffsetFlushTrailing(cur); + break; - // cur has no data and was already printed - // goto prev, augment it's offset and try again + // cur element was visited before and has no data }else{ - cur = cur.prev; - cur = augmentOffsetFlushTrailing(cur); - rollback = true; + + // head has no more data, terminate + if(cur.prev === null){ + return; + + // cur has no data and was already printed + // goto prev, augment it's offset and try again + }else{ + cur = cur.prev; + cur = this.augmentOffsetFlushTrailing(cur); + rollback = true; + } } } - } - // the last element was reached and it has data != null - if(cur.next === null){ - cur = augmentOffsetFlushTrailing(cur); - break; + // the last element was reached and it has data != null + if(cur.next === null){ + cur = this.augmentOffsetFlushTrailing(cur); + break; + } + + // if it's not a rollback run - explore the next element + if(!rollback) + cur = cur.next; } - // if it's not a rollback run - explore the next element - if(!rollback) - cur = cur.next; + /* + Send joined data raw to the end-user accordingly to the constructRow implementation. + + It should be stressed, that after the first element with data == null, all subsequent elements + have no valid data. Also, as long as offsets are used to check if a given element was + already visited (printed) or not, the offsets should not be modified within constructRow function, + and it's interpretation is not direct. + */ + + let row_string = this.constructRow(this.list.head); + await httpWritableStream.write(row_string); + + }catch(err){ + /* + We can't throw an error to Express server at this stage because the response Content-Type + was already sent. So we can try to attach it to the end of file. + */ + console.log(err); + await httpWritableStream.write(`{error : ${err.message}}\n`); + return; } + } + }; - /* - Send joined data raw to the end-user accordingly to the constructRow implementation. - It should be stressed, that after the first element with data == null, all subsequent elements - have no valid data. Also, as long as offsets are used to check if a given element was - already visited (printed) or not, the offsets should not be modified within constructRow function, - and it's interpretation is not direct. - */ + /** + * defineFindFunction - Function use offset to retrieve corresponding data for the current list element according + * to the current offset. This function will renew the cur.model_adj.data and augment + * the cur.model_adj.offset field. If there is no data for the current offset, the + * cur.model_adj.data will be set to null. + * + * It is assumed, that cur->prev element has already initialized it's data field. If cur->prev is null, it means that + * we are working with the list head. If after calling this function, the cur.model_adj.data is null, + * it means that there is nothing mode to do, and the JOIN process has successfully completed. + * + * @param {object} cur is a current element of the linked-list + * @return {function} returns a function used inside generic algorithm + */ + + defineFindFunction(cur){ + + + if(cur.prev === null){ + + // cur is the head element of the list + return async function(cur, context){ + + // for head getter function has to be estimated just once + if( ! cur.model_adj.func_getter) + cur.model_adj.func_getter = resolvers[inflection.pluralize(cur.model_adj.name)]; + + // get record from database for the given offset + // an output is an array that have one or zero elements + cur.model_adj.data = await cur.model_adj.func_getter(cur.model_adj.search_params, context); + + if( cur.model_adj.data.length === 0 ) { + cur.model_adj.data = null; + }else{ + cur.model_adj.data = cur.model_adj.data[0]; + } + + return cur; + } - let row_string = constructRow(list.head); - await httpWritableStream.write(row_string); + } else { - }catch(err){ /* - We can't throw an error to Express server at this stage because the response Content-Type - was already sent. So we can try to attach it to the end of file. + Here an explicit check is applied to detect for the association getter function in the cur.prev data model. + At the same time this is a validator (see the "else" option). */ - console.log(err); - await httpWritableStream.write(`{error : ${err.message}}\n`); - return; - } - } -}; -/* - Function use offset to retrieve corresponding data for the current list element according - to the current offset. This function will renew the cur.model_adj.data and augment - the cur.model_adj.offset field. If there is no data for the current offset, the - cur.model_adj.data will be set to null. + let model_prev = models[cur.prev.model_adj.name]; - It is assumed, that cur->prev element has already initialized it's data field. If cur->prev is null, it means that - we are working with the list head. If after calling this function, the cur.model_adj.data is null, - it means that there is nothing mode to do, and the JOIN process has successfully completed. -*/ + const as_name = cur.prev.model_adj.association_name; + if( ! as_name ) throw Error('"assoc" structure is required, see the docs'); -defineFindFunction = function (cur){ + //<%- nameLc -%>.prototype.<%=associations_one[i].name%> + let func_toOneGetter = model_prev.prototype[as_name]; + //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter + let func_toManyGetter = model_prev.prototype[`${as_name}Filter`]; - if(cur.prev === null){ + if(typeof func_toOneGetter === "function"){ - // cur is the head element of the list - return async function(cur, context){ + // there is just one cur element can be found from the cur.prev that + // corresponds to the hasOne or belongsTo of the prev->cur association type + return async function(cur, context){ + const as_name = cur.prev.model_adj.association_name; - // for head getter function has to be estimated just once - if( ! cur.model_adj.func_getter) - cur.model_adj.func_getter = resolvers[inflection.pluralize(cur.model_adj.name)]; + if(cur.model_adj.search_params.pagination.offset > 0){ + cur.model_adj.data = null; + }else{ + cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); + } - // get record from database for the given offset - // an output is an array that have one or zero elements - cur.model_adj.data = await cur.model_adj.func_getter(cur.model_adj.search_params, context); + return cur; + } + } else if(typeof func_toManyGetter === "function"){ - if( cur.model_adj.data.length === 0 ) { - cur.model_adj.data = null; - }else{ - cur.model_adj.data = cur.model_adj.data[0]; + return async function(cur, context){ + + // get record from database for the current offset (it comes inside cur.model_adj.search_params data structure) + // an output is an array that would have one (because limit is always 1) or zero elements (if nothing was found) + cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); + + // set data to null explicitly or remove an array wrapper (anyway there is just one element) + if( cur.model_adj.data.length === 0 ){ + cur.model_adj.data = null; + }else{ + cur.model_adj.data = cur.model_adj.data[0]; + } + + return cur; + } + } else{ + /* + If you get this error, it means that there is no explicit link between cur.prev and cur elements. + For example, assume that model A belongsTo model B. However, the madel B does not have a corresponding + hasMany or hasOne association with A. If you try to make a JOIN in the order B -> A, you will get + this "No association" exception. However, if you JOIN these models in the order A -> B, the corresponding + association resolver will be found. + + Currently all associations are inverse, so this error should never happen. + */ + + throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); } - return cur; } - } else { + }; - /* - Here an explicit check is applied to detect for the association getter function in the cur.prev data model. - At the same time this is a validator (see the "else" option). - */ - let model_prev = models[cur.prev.model_adj.name]; - const as_name = cur.prev.model_adj.assoc.as_name; - if( ! as_name ) throw Error('"assoc" structure is required, see the docs'); - //<%- nameLc -%>.prototype.<%=associations_one[i].name%> - let func_toOneGetter = model_prev.prototype[as_name]; - //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter - let func_toManyGetter = model_prev.prototype[`${as_name}Filter`]; + /** + * defineSearchParams - a helper function fills up a serach_params data structure. It's 'search' and 'order' + * elements would never change during the given transmission session. However the pagination + * parameter is important. The limit shell always be 1, and the offset is internal parameter of + * the current algorithm. It is prohibited to alter offset values from the outside world. + * + * @param {object} cur is a current element of the linked-list + * @return {object} returns a structure of search params + */ + defineSearchParams(cur){ + let search_params = {}; - if(typeof func_toOneGetter === "function"){ + search_params.pagination = { + offset : 0, + limit : 1 + }; - // there is just one cur element can be found from the cur.prev that - // corresponds to the hasOne or belongsTo of the prev->cur association type - return async function(cur, context){ - const as_name = cur.prev.model_adj.assoc.as_name; + if( cur.model_adj.search ) + search_params.search = cur.model_adj.search; - if(cur.model_adj.search_params.pagination.offset > 0){ - cur.model_adj.data = null; - }else{ - cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); - } + if( cur.model_adj.order ) + search_params.order = cur.model_adj.order; - return cur; - } - } else if(typeof func_toManyGetter === "function"){ + return search_params; + }; - return async function(cur, context){ - // get record from database for the current offset (it comes inside cur.model_adj.search_params data structure) - // an output is an array that would have one (because limit is always 1) or zero elements (if nothing was found) - cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); - // set data to null explicitly or remove an array wrapper (anyway there is just one element) - if( cur.model_adj.data.length === 0 ){ - cur.model_adj.data = null; - }else{ - cur.model_adj.data = cur.model_adj.data[0]; - } - return cur; - } - } else{ - /* - If you get this error, it means that there is no explicit link between cur.prev and cur elements. - For example, assume that model A belongsTo model B. However, the madel B does not have a corresponding - hasMany or hasOne association with A. If you try to make a JOIN in the order B -> A, you will get - this "No association" exception. However, if you JOIN these models in the order A -> B, the corresponding - association resolver will be found. - */ - - throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); + /** + * augmentOffsetFlushTrailing - Helper function is used to augment offset of the "cur" element. + * In this case offsets and data of the all trailing elements became invalid + * and shell be flushed. + * + * @param {object} cur is a current element of the linked-list + * @return {object} returns a cur element with offset augmented that points on to the cleaned up tail + */ + augmentOffsetFlushTrailing(cur){ + cur.model_adj.search_params.pagination.offset++; + let next = cur.next; + while(next !== null){ + next.model_adj.search_params.pagination.offset = 0; + next.model_adj.data = null; + next = next.next; } + return cur; + }; + + + /** + * constructRow - basic implementation of the constructRow function that prints model names and id's + *of the found elements. It is useful for testing purposes. + *... + *individual[id:458] ->transcript_count[id:6] + *individual[id:459] ->transcript_count[id:2] + *individual[id:460] + *individual[id:461] + * + * @param {object} head head of the linked list + * @return {string} joined line + */ + constructRow(head){ + let str = ""; + + let cur = head; + do{ + str = str.concat(`${cur.model_adj.name}[`); + str = str.concat(`id:${cur.model_adj.data.id}] `); + + if(cur.next !== null && cur.next.model_adj.data === null) + break; + if(cur.next !== null) + str = str.concat("->"); + + }while(null !== (cur = cur.next)); + + str = str.concat("\n"); + + return str; } + }; +// ********************************************************************************************************************* -/* - This helper function fills up a serach_params data structure. It's 'search' and 'order' - elements would never change during the given transmission session. However the pagination - parameter is important. The limit shell always be 1, and the offset is internal parameter of - the current algorithm. It is prohibited to alter offset values from the outside world. - */ -defineSearchParams = function(cur){ - let search_params = {}; - search_params.pagination = { - offset : 0, - limit : 1 - }; +class JoinModelsJSON extends JoinModels{ + /** + * constructRow - create text string for the joined line in JSON format + * + * Example output for filtered columns: + * ... + * {"transcript_count.id":8,"individual.id":463} + * {"transcript_count.id":9,"individual.id":null} + * {"transcript_count.id":10,"individual.id":462} + * ... + * + * @param {object} head head of the linked list + * @return {string} a text string that will be sent to the service client + */ - //TODO: Test filtering - if( ! cur.model_adj.search ) - search_params.search = cur.model_adj.search; + constructRow(head){ - //TODO: Test ordering - if( ! cur.model_adj.order ) - search_params.order = cur.model_adj.order; + let cur = head; + let joined_data = {}; + do{ - return search_params; -}; + let data = {}; + if(cur.model_adj.data === null){ + for (let attr of cur.model_adj.attributes) { + data[cur.model_adj.name + "." + attr] = null; + } + }else{ + data = _.pick(cur.model_adj.data, cur.model_adj.attributes); + + for(let old_key of Object.keys(data)){ + let new_key = cur.model_adj.name + "." + old_key; + Object.defineProperty(data, new_key, + Object.getOwnPropertyDescriptor(data, old_key)); + delete data[old_key]; + } + } + Object.assign(joined_data, data); + }while(null !== (cur = cur.next)); -/* - This helper function is used to augment offset of the "cur" element. - In this case offsets and data of the all trailing elements became invalid - and shell be flushed. - */ -augmentOffsetFlushTrailing = function(cur){ - cur.model_adj.search_params.pagination.offset++; - let next = cur.next; - while(next !== null){ - next.model_adj.search_params.pagination.offset = 0; - next.model_adj.data = null; - next = next.next; - } - return cur; + return JSON.stringify(joined_data) + "\n"; + }; }; +class JoinModelsCSV extends JoinModels{ + /** + * Internal class parameters + */ + constructor(){ + super(); + // in CSV format first line is the title + this.csv_header = true; + } -/* - The basic implementation of the constructRow function that prints model names and id's - of the found elements. It is used for testing. + /** + * constructRow - create text string for the joined line in CSV format + * + * Example output for filtered columns: + * + * transcript_count.id,individual.id + * 8,463 + * 9,null + * 10,462 + * ... + * + * @param {object} head head of the linked list + * @return {string} a text string that will be sent to the service client + */ + constructRow(head){ - ... - individual[id:458] ->transcript_count[id:6] - individual[id:459] ->transcript_count[id:2] - individual[id:460] - individual[id:461] - individual[id:462] ->transcript_count[id:7] - individual[id:462] ->transcript_count[id:10] - individual[id:463] ->transcript_count[id:8] - ... - */ + let cur = head; + + let str = ""; + let header = ""; + + do{ + + for (let attr of cur.model_adj.attributes){ + + if(this.csv_header) + header += cur.model_adj.name + "." + attr + ","; -constructRow = function(head){ + if(cur.model_adj.data === null){ + str += "null,"; + }else{ + str += cur.model_adj.data[attr] + ","; + } + } + + }while(null !== (cur = cur.next)); + + if(this.csv_header){ + this.csv_header = false; + header = header.replace(/.$/,"\n"); + header += str; + str = header; + } + + str = str.replace(/.$/,"\n"); + + return str; + }; +} + + + +module.exports.JoinModels = JoinModels; +module.exports.JoinModelsJSON = JoinModelsJSON; +module.exports.JoinModelsCSV = JoinModelsCSV; + +/********************************************************** + +CURL tests (copy-paste to console): - let str = ""; +curl -d '[ { "name" : "individual", "association_name" : "transcript_counts" }, { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join - let cur = head; - do{ - str = str.concat(`${cur.model_adj.name}[`); - str = str.concat(`id:${cur.model_adj.data.id}] `); - if(cur.next !== null && cur.next.model_adj.data === null) - break; +curl -d '[ { "name" : "transcript_count", "association_name" : "individual" }, { "name" : "individual"} ]' -H "Content-Type: application/json" http://localhost:3000/join - if(cur.next !== null) - str = str.concat("->"); - }while(null !== (cur = cur.next)); +curl -d '[ { "name" : "transcript_count", "association_name" : "individual", "attributes" : ["id"] }, { "name" : "individual", "attributes" : ["id"]} ]' -H "Content-Type: application/json" http://localhost:3000/join - str = str.concat("\n"); - return str; -}; \ No newline at end of file +curl -d '[ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]' -H "Content-Type: application/json" http://localhost:3000/join +*/ \ No newline at end of file From 88b09fc174def58839dfa1542b93baead984ffcf Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 29 Mar 2019 05:48:49 -0600 Subject: [PATCH 074/125] ACL in the join-models added --- server.js | 17 +++++++++++++---- utils/join-models.js | 30 ++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/server.js b/server.js index 20f0c85..d8779ba 100644 --- a/server.js +++ b/server.js @@ -88,13 +88,22 @@ app.use('/join', cors(), (req, res) => { if (!req.is('application/json')) return res.status(415).send({error: "JSON Content-Type expected"}); - let joinModels = new JOIN.JoinModels(); + let context = { + request: req, + acl: acl + }; + + let joinModels = new JOIN.JoinModelsJSON(context); joinModels.run(req.body, res).then(() => { res.end(); - }).catch(err => { - console.log(err); - res.status(500).send({error: err.message}); + }).catch(error => { + let formattedError = { + message: error.message, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", + path: error.path + }; + res.status(500).send(formattedError); }); }); diff --git a/utils/join-models.js b/utils/join-models.js index e11bf14..571551e 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -1,14 +1,13 @@ -// TODO: document all in off. docs - - const _ = require('lodash'); // TODO: Use generic index +// const models = require('../models_index'); const models = require('../models/index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); +const checkAuthorization = require('./check-authorization'); let LinkedList = require('linked-list'); @@ -64,6 +63,11 @@ let LinkedList = require('linked-list'); * } * ] * + * PERMISSIONS + * + * The user role should have a "batch_download" permission for all models defined in the + * "modelAdjacencies" input parameter. + * */ class JoinModels { @@ -71,7 +75,7 @@ class JoinModels { /** * Internal class parameters */ - constructor(){ + constructor(context){ // a linked list to be initialized from the input adjacency array // this list will always keep the current model and the next model that is more useful than @@ -79,8 +83,7 @@ class JoinModels { this.list = new LinkedList; // request context - //TODO: Remove user session stub (this would require login session on the client side) - this.context = { acl : null }; + this.context = context; } /** @@ -111,6 +114,9 @@ class JoinModels { // iterate over the list and add some useful information to it's elements let cur = this.list.head; do{ + // check for permission to make batch_download for all models listed + if( ! await checkAuthorization(this.context, cur.model_adj.name, 'batch_download')) + return new Error(`You don't have authorization to perform batch download for the ${cur.model_adj.name} model`); // required on this step input data validation if( ! cur.model_adj.name ) throw Error(`Model name is not defined in ${JSON.stringify(cur.model_adj)}`); @@ -434,6 +440,14 @@ class JoinModels { class JoinModelsJSON extends JoinModels{ + + /** + * Internal class parameters + */ + constructor(context){ + super(context); + } + /** * constructRow - create text string for the joined line in JSON format * @@ -484,8 +498,8 @@ class JoinModelsCSV extends JoinModels{ /** * Internal class parameters */ - constructor(){ - super(); + constructor(context){ + super(context); // in CSV format first line is the title this.csv_header = true; From ec4622a707ff5e44808efeac9e1dbc807ddcb025 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 29 Mar 2019 07:52:00 -0600 Subject: [PATCH 075/125] check models_index.js --- utils/join-models.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 571551e..fc212ce 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -3,8 +3,8 @@ const _ = require('lodash'); // TODO: Use generic index -// const models = require('../models_index'); -const models = require('../models/index'); +const models = require('../models_index'); +//const models = require('../models/index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); const checkAuthorization = require('./check-authorization'); From 22d90c8e52db4e0d175f3b2217397f481e947ec2 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Fri, 29 Mar 2019 08:01:12 -0600 Subject: [PATCH 076/125] back to regular index --- utils/join-models.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index fc212ce..571551e 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -3,8 +3,8 @@ const _ = require('lodash'); // TODO: Use generic index -const models = require('../models_index'); -//const models = require('../models/index'); +// const models = require('../models_index'); +const models = require('../models/index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); const checkAuthorization = require('./check-authorization'); From 13ef75f0542b4f60ffdbfd9b760dfda0900e75bb Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Mon, 1 Apr 2019 03:10:22 -0600 Subject: [PATCH 077/125] Global index with JOIN tests --- models_index.js | 3 ++- utils/join-models.js | 12 +++++++++--- utils/login.js | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/models_index.js b/models_index.js index 0a8d152..974d55d 100644 --- a/models_index.js +++ b/models_index.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const path = require('path') +const path = require('path'); sequelize = require('./connection'); var models = {}; @@ -14,6 +14,7 @@ fs.readdirSync("./models") return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); }) .forEach(function(file) { + console.log(file); let model = sequelize['import'](path.join("./models", file)); diff --git a/utils/join-models.js b/utils/join-models.js index 571551e..5877d16 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -1,10 +1,14 @@ +// TODO: JOIN: do not print anything if there is no data found, print that there is no data found + + +//TODO: Web service generates a class object that is incompatible with +//TODO: sequelize object. All types of models shell have the same interface to +//TODO: proceed with generic JOIN. That's clearly a new issue. const _ = require('lodash'); -// TODO: Use generic index -// const models = require('../models_index'); -const models = require('../models/index'); +const models = require('../models_index'); const resolvers = require('../resolvers/index'); const inflection = require('inflection'); const checkAuthorization = require('./check-authorization'); @@ -123,7 +127,9 @@ class JoinModels { if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); // store raw names of the model attributes if not given at input + console.log(cur.model_adj.name); let model = models[cur.model_adj.name]; + console.log(model); if( ! cur.model_adj.attributes ) { cur.model_adj.attributes = []; for (let attribute_name of Object.keys(model.rawAttributes)) diff --git a/utils/login.js b/utils/login.js index 4437e3e..ee39de4 100644 --- a/utils/login.js +++ b/utils/login.js @@ -1,6 +1,6 @@ -const bcrypt = require('bcrypt') -const jsonwebtoken = require('jsonwebtoken') -const user = require('../models/index').user +const bcrypt = require('bcrypt'); +const jsonwebtoken = require('jsonwebtoken'); +const user = require('../models_index').user; module.exports = { From d8d400559510dbdf6c3d8aab28e39986cdce2b4d Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 1 Apr 2019 17:56:43 -0600 Subject: [PATCH 078/125] add date types in schema, dictionary for graphql-sequelize types --- server.js | 4 ++++ utils/graphql-sequelize-types.js | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 utils/graphql-sequelize-types.js diff --git a/server.js b/server.js index 9644000..cfafbe1 100644 --- a/server.js +++ b/server.js @@ -59,6 +59,10 @@ var merged_schema = mergeSchema(path.join(__dirname, './schemas')); console.log(merged_schema); var Schema = buildSchema(merged_schema); + /*set scalar types for dates */ + Object.assign(Schema._typeMap.DateTime, GraphQLDateTime); + Object.assign(Schema._typeMap.Date, GraphQLDate); + Object.assign(Schema._typeMap.Time, GraphQLTime); /* Resolvers*/ var resolvers = require('./resolvers/index'); diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js new file mode 100644 index 0000000..698a706 --- /dev/null +++ b/utils/graphql-sequelize-types.js @@ -0,0 +1,13 @@ +/* +Data types dictionary from graphql-type to sequelize-type +*/ + +module.exports = { + + "Int" : 'INTEGER', + "String": 'STRING', + "Float": 'FLOAT', + "Date": "DATEONLY", + "Time": "TIME", + "DateTime": "DATE" +} From 9e6d23cb28c6464db6386bb1af172cae1e7df9f6 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 1 Apr 2019 23:53:16 -0600 Subject: [PATCH 079/125] boolean type added to dictionary --- utils/graphql-sequelize-types.js | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js index 698a706..64ef1d2 100644 --- a/utils/graphql-sequelize-types.js +++ b/utils/graphql-sequelize-types.js @@ -7,6 +7,7 @@ module.exports = { "Int" : 'INTEGER', "String": 'STRING', "Float": 'FLOAT', + "Boolean": 'BOOLEAN', "Date": "DATEONLY", "Time": "TIME", "DateTime": "DATE" From 635619002c1e627865cb99f29954df1350241915 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 2 Apr 2019 00:25:04 -0600 Subject: [PATCH 080/125] graphql-iso-date library installed --- package.json | 13 +++++++------ server.js | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7cb7b68..c331c24 100644 --- a/package.json +++ b/package.json @@ -23,23 +23,24 @@ "express-jwt": "^5.3.1", "faker": "^4.1.0", "graphql": "^0.13.1", + "graphql-iso-date": "^3.6.1", + "joi": "^14.3.1", "jsonwebtoken": "^8.2.0", + "linked-list": "^2.0.0", "lodash": "^4.17.5", "mathjs": "^5.2.0", "merge-graphql-schemas": "^1.5.1", "mysql2": "^1.6.4", + "nodemailer": "^5.1.1", + "nodemailer-smtp-transport": "^2.7.4", "pg": "^7.4.1", "pg-hstore": "^2.3.2", "sequelize": "^4.35.2", "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", - "uuidv4": "^2.0.0", - "xlsx": "^0.12.11", "sync-request": "^6.0.0", - "nodemailer" : "^5.1.1", - "nodemailer-smtp-transport": "^2.7.4", - "joi" : "^14.3.1", - "linked-list": "^2.0.0" + "uuidv4": "^2.0.0", + "xlsx": "^0.12.11" }, "devDependencies": { "mocha": "^5.2.0", diff --git a/server.js b/server.js index cfafbe1..58618b4 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); const globals = require('./config/globals'); const JOIN = require('./utils/join-models'); + const {GraphQLDateTime, GraphQLDate, GraphQLTime } = require('graphql-iso-date'); var { buildSchema From e202310628d16eb39b31208b87dc4f72c70ce653 Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Tue, 2 Apr 2019 16:42:51 -0600 Subject: [PATCH 081/125] Send zip in batch upload e-mail --- package.json | 3 ++- utils/email.js | 19 ++++++++--------- utils/file-tools.js | 50 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 7cb7b68..4deb97c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "nodemailer" : "^5.1.1", "nodemailer-smtp-transport": "^2.7.4", "joi" : "^14.3.1", - "linked-list": "^2.0.0" + "linked-list": "^2.0.0", + "adm-zip": "^0.4.13" }, "devDependencies": { "mocha": "^5.2.0", diff --git a/utils/email.js b/utils/email.js index afbfb08..ebd9d51 100644 --- a/utils/email.js +++ b/utils/email.js @@ -1,9 +1,10 @@ const NodeMailer = require('nodemailer'); const SmtpTransport = require('nodemailer-smtp-transport'); const Globals = require('../config/globals'); +const path = require('path'); module.exports = { - sendEmail: function (dst_email, subject, message){ + sendEmail: function (dst_email, subj, message, att){ console.log(`${dst_email}, ${message}, ${Globals.MAIL_ACCOUNT}, ${Globals.MAIL_PASSWORD}`); let transporter = NodeMailer.createTransport(SmtpTransport({ @@ -19,16 +20,12 @@ module.exports = { let mailOptions = { from: Globals.MAIL_ACCOUNT, to: dst_email, - subject: subject, - text: message + subject: subj, + text: message, + attachments: att }; - transporter.sendMail(mailOptions, function(error, info){ - if (error) { - console.log(error); - } else { - console.log('Email sent: ' + info.response); - } - }); + + return transporter.sendMail(mailOptions); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/utils/file-tools.js b/utils/file-tools.js index 4ed9260..d71209f 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -5,6 +5,7 @@ const csv_parse = require('csv-parse'); const fs = require('fs'); const awaitifyStream = require('awaitify-stream'); const validatorUtil = require('./validatorUtil'); +const admZip = require('adm-zip'); /** @@ -61,7 +62,14 @@ exports.parseXlsx = function(bstr) { XLSX.utils.sheet_to_json( workbook.Sheets[sheet_name_list[0]]) ); -} +}; + +exports.deleteIfExists = function (path){ + fs.exists(path, function(exists){ + if(exists) + fs.unlinkSync(path); + }); +}; /** * Parse by streaming a csv file and create the records in the correspondant table @@ -77,6 +85,13 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { console.log("TYPEOF", typeof model); // Wrap all database actions within a transaction: let transaction = await model.sequelize.transaction(); + + let addedFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + ".json"; + let addedZipFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + ".zip"; + + console.log(addedFilePath); + console.log(addedZipFilePath); + try { // Pipe a file read-stream through a CSV-Reader and handle records asynchronously: let csvStream = awaitifyStream.createReader( @@ -88,6 +103,11 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { ) ); + // Create an output file stream + let addedRecords = awaitifyStream.createWriter( + fs.createWriteStream(addedFilePath) + ); + let record; let errors = []; @@ -105,6 +125,12 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { await model.create(record, { transaction: transaction + }).then(created => { + + // this is async, here we just push new line into the parallel thread + // synchronization goes at endAsync; + addedRecords.writeAsync(`${JSON.stringify(created)}\n`); + }).catch(error => { console.log(`Caught sequelize error during CSV batch upload: ${JSON.stringify(error)}`); error.record = record; @@ -114,6 +140,9 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { } } + // close the addedRecords file so it can be sent afterwards + await addedRecords.endAsync(); + if(errors.length > 0) { let message = "Some records could not be submitted. No database changes has been applied.\n"; message += "Please see the next list for details:\n"; @@ -127,8 +156,27 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { await transaction.commit(); + // zip comitted data and return a corresponding file path + let zipper = new admZip(); + zipper.addLocalFile(addedFilePath); + await zipper.writeZip(addedZipFilePath); + + console.log(addedZipFilePath); + + // At this moment the parseCsvStream caller is responsible in deleting the + // addedZipFilePath + return addedZipFilePath; + } catch (error) { + await transaction.rollback(); + + exports.deleteIfExists(addedFilePath); + exports.deleteIfExists(addedZipFilePath); + throw error; + + } finally { + exports.deleteIfExists(addedFilePath); } }; From 416b7be53cc7b6686171b9a61f201db80f9e5951 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 4 Apr 2019 12:41:13 -0600 Subject: [PATCH 082/125] helper added for getting models attributes info --- utils/helper.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/utils/helper.js b/utils/helper.js index 4358207..984b5cd 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -174,3 +174,70 @@ module.exports.vueTable = function(req, model, strAttributes) { } }) } + + + /** + * modelAttributes - Return info about each column in the model's table + * + * @param {Object} model Sequelize model from which the info will be given. + * @return {Array} Array of objects, each object contains info for each attribute in the model + */ + modelAttributes = function(model) { + return model.sequelize.query( + "SELECT column_name, data_type, is_nullable, column_default " + + "FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + + model.tableName + "'", { + type: model.sequelize.QueryTypes.SELECT + } + ) + } + + //attributes to discard + discardModelAttributes = ['createdAt', 'updatedAt'] + + + /** + * filterModelAttributesForCsv - Filter attributes from a given model +f * + * @param {Object} model Sequelize model from which the attributes will be filtered + * @param {Array} discardAttrs Array of attributes to discard + * @return {Array} Filtered attributes + */ + filterModelAttributesForCsv = function(model, discardAttrs) { + discardAttrs = discardAttrs || discardModelAttributes + modelPrimaryKey = model.primaryKeyField + if (modelPrimaryKey) + discardAttrs = discardAttrs.concat([modelPrimaryKey]) + return modelAttributes(model).then(function(x) { + return x.filter(function(i) { + return discardAttrs.indexOf(i.column_name) < 0 + }) + }) + } + + + /** + * csvTableTemplate - Returns template of model, i.e. header of each column an its type + * + * @param {Object} model Sequelize model from which the template will be returned. + * @param {Array} discardAttrs Attributes to discard from the template + * @return {Array} Array of strings, one for header and one for the attribute't type. + */ + module.exports.csvTableTemplate = function(model, discardAttrs) { + return filterModelAttributesForCsv(model, + discardAttrs).then(function(x) { + csvHeader = [] + csvExmplRow = [] + x.forEach(function(i) { + csvStr = i.data_type + if (i.is_nullable.toLowerCase() === 'false' || i.is_nullable.toLowerCase() === + 'no' || i.is_nullable === 0) + csvStr += ",required" + if (i.column_default) + csvStr += ",default:" + i.column_default + csvHeader = csvHeader.concat([i.column_name]) + csvExmplRow = csvExmplRow.concat([csvStr]) + }) + return [csvHeader.join(','), csvExmplRow.join(',')] + }) + } From 18d953032792ba46732aaee1e78188cc88a6b5aa Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Wed, 10 Apr 2019 10:05:59 -0500 Subject: [PATCH 083/125] Error on batch upload corrected --- utils/file-tools.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index d71209f..a8cf905 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -65,9 +65,9 @@ exports.parseXlsx = function(bstr) { }; exports.deleteIfExists = function (path){ - fs.exists(path, function(exists){ - if(exists) - fs.unlinkSync(path); + console.log(`Removing ${path}`); + fs.unlink(path, function (err){ + // file may be already deleted }); }; From b9f68c842914f00ed14f0ad43065f46b6e72be8f Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Wed, 10 Apr 2019 12:07:24 -0500 Subject: [PATCH 084/125] Closes #14 batch upload explicit nulls --- utils/file-tools.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index a8cf905..3b88abf 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -64,6 +64,12 @@ exports.parseXlsx = function(bstr) { ); }; +/** + * Function that will delete a file if it exists and is insensitive to the + * case when a file not exist. + * + * @param {String} path - A path to the file + */ exports.deleteIfExists = function (path){ console.log(`Removing ${path}`); fs.unlink(path, function (err){ @@ -71,6 +77,29 @@ exports.deleteIfExists = function (path){ }); }; +/** + * Function deletes properties that contain string values "NULL" or "null". + * + * @param {Object} pojo - A plain old JavaScript object. + * + * @return {Object} A modified clone of the argument pojo in which all String + * "NULL" or "null" values are replaced with literal nulls. + */ +exports.replacePojoNullValueWithLiteralNull = function(pojo) { + if (pojo === null) { + return null + } + let res = Object.assign({}, pojo); + Object.keys(res).forEach((k) => { + if(res[k].localeCompare("NULL", undefined, { sensitivity: 'accent' }) === 0){ + //res[k] = null + delete res[k]; + } + }); + return res +}; + + /** * Parse by streaming a csv file and create the records in the correspondant table * @function @@ -80,6 +109,7 @@ exports.deleteIfExists = function (path){ * @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). */ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { + if (!delim) delim = ","; if (typeof cols === 'undefined') cols = true; console.log("TYPEOF", typeof model); @@ -90,7 +120,7 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { let addedZipFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + ".zip"; console.log(addedFilePath); - console.log(addedZipFilePath); + console.log(addedZipFilePath); try { // Pipe a file read-stream through a CSV-Reader and handle records asynchronously: @@ -113,6 +143,10 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { while (null !== (record = await csvStream.readAsync())) { + console.log(record); + record = exports.replacePojoNullValueWithLiteralNull(record); + console.log(record); + let error = validatorUtil.ifHasValidatorFunctionInvoke('validatorForCreate', model, record); if (!!error) { From e98519560d618dfdd8f8a6d8cc66f87b6b9a7398 Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Thu, 11 Apr 2019 17:17:52 -0500 Subject: [PATCH 085/125] Closes #12 instance of GraphQlSchema --- utils/graphql_schema.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 utils/graphql_schema.js diff --git a/utils/graphql_schema.js b/utils/graphql_schema.js new file mode 100644 index 0000000..2837eb4 --- /dev/null +++ b/utils/graphql_schema.js @@ -0,0 +1,33 @@ +const path = require('path'); + +const { buildSchema } = require('graphql'); +const mergeSchema = require(path.join(__dirname,'../','utils','merge-schemas')); + +module.exports.schema = buildSchema(mergeSchema(path.join(__dirname,'../','./schemas'))); + +/** + * Each model deefined in it's *.json file can have a description field. However, we also add + * internal annotations within this filed. It is possible to search for fields of the given + * model with respect to the presence of an annotation inside of it's description. + * @function getModelFieldByAnnotation + * @param {string} model_name - The name of the data model to be explored. + * @param {string} annotation - An annotation name, for example "@original-field" + * @return {Array} - an array of model field names that contain a given annotation in it's + * description + */ +module.exports.getModelFieldByAnnotation = function(model_name, annotation){ + let schema = module.exports.schema; + + let model_type = schema.getType(model_name); + if(! schema.getType(model_name)) + throw new Error(`Model ${model_name} not exist`); + + let fields = model_type._fields; + + return Object.keys(fields).filter( a => { + let desc = fields[a].description; + if(desc) + return desc.toString().includes(annotation); + return false; + }) +}; \ No newline at end of file From a4a7784585ddbf35356f264f50321e0f1dae8a97 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Fri, 12 Apr 2019 15:59:35 +0200 Subject: [PATCH 086/125] Fixed bug #15 'Switch requiring of specific models to new global module' See https://github.com/ScienceDb/graphql-server/issues/15 --- utils/join-models.js | 7 ++++--- utils/login.js | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 5877d16..e17795f 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -8,8 +8,9 @@ const _ = require('lodash'); -const models = require('../models_index'); -const resolvers = require('../resolvers/index'); +const path = require('path'); +const models = require(path.join(__dirname, '..', 'models_index.js')); +const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); const inflection = require('inflection'); const checkAuthorization = require('./check-authorization'); let LinkedList = require('linked-list'); @@ -582,4 +583,4 @@ curl -d '[ { "name" : "transcript_count", "association_name" : "individual", "a curl -d '[ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]' -H "Content-Type: application/json" http://localhost:3000/join -*/ \ No newline at end of file +*/ diff --git a/utils/login.js b/utils/login.js index ee39de4..7b2d6d4 100644 --- a/utils/login.js +++ b/utils/login.js @@ -1,10 +1,10 @@ -const bcrypt = require('bcrypt'); -const jsonwebtoken = require('jsonwebtoken'); -const user = require('../models_index').user; +const bcrypt = require('bcrypt') +const jsonwebtoken = require('jsonwebtoken') +const path = require('path') +const user = require(path.join(__dirname, '..', 'models_index.js')).user module.exports = { - /** * login - Search for email in users table and returns a webtoken if the password is valid. * From d728fc50cea3923ac1025f69115e866f68c01be9 Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Sat, 20 Apr 2019 16:12:31 -0500 Subject: [PATCH 087/125] web service --- utils/join-models.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index e17795f..239e0af 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -13,6 +13,7 @@ const models = require(path.join(__dirname, '..', 'models_index.js')); const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); const inflection = require('inflection'); const checkAuthorization = require('./check-authorization'); +const schema = require('./graphql_schema'); let LinkedList = require('linked-list'); @@ -128,19 +129,19 @@ class JoinModels { if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); // store raw names of the model attributes if not given at input - console.log(cur.model_adj.name); - let model = models[cur.model_adj.name]; - console.log(model); + console.log("Starting"); + let all_attributes = schema.getModelFieldByAnnotation(cur.model_adj.name,'@original-field'); + console.log(`All attributes for model: ${cur.model_adj.name} are >>> ${all_attributes}`); if( ! cur.model_adj.attributes ) { - cur.model_adj.attributes = []; - for (let attribute_name of Object.keys(model.rawAttributes)) - cur.model_adj.attributes.push(attribute_name); + cur.model_adj.attributes = all_attributes; }else{ // check that specified attributes are correct for (let attribute_name of cur.model_adj.attributes) - if( ! model.rawAttributes[attribute_name]) + if( ! all_attributes.includes(attribute_name)) throw Error(`Requested attribute '${attribute_name}' is not defined in the model '${cur.model_adj.name}'`); } + //TODO: Kill this + // current data (is initialized by the 'func_find' call) cur.model_adj.data = null; @@ -583,4 +584,7 @@ curl -d '[ { "name" : "transcript_count", "association_name" : "individual", "a curl -d '[ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]' -H "Content-Type: application/json" http://localhost:3000/join + +curl -d '[ { "name" : "transcript_count", "association_name" : "aminoacidsequence", "attributes" : [ "id", "gene"] } , {"name": "aminoacidsequence"} ]' -H "Content-Type: application/json" http://localhost:3000/join + */ From 11c698863fa778aca71b7b1ee4899b9956762314 Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Sat, 20 Apr 2019 18:17:17 -0500 Subject: [PATCH 088/125] web service --- utils/join-models.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 239e0af..ed987a4 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -140,8 +140,6 @@ class JoinModels { if( ! all_attributes.includes(attribute_name)) throw Error(`Requested attribute '${attribute_name}' is not defined in the model '${cur.model_adj.name}'`); } - //TODO: Kill this - // current data (is initialized by the 'func_find' call) cur.model_adj.data = null; @@ -345,7 +343,8 @@ class JoinModels { Currently all associations are inverse, so this error should never happen. */ - throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected`); + throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected,` + + `please check for ${as_name} or ${as_name}Filter functions`); } } From 39864c0a03a5eafa4ea9b70537f0d3775b609041 Mon Sep 17 00:00:00 2001 From: JKolokoltsev Date: Sun, 21 Apr 2019 22:32:48 -0500 Subject: [PATCH 089/125] JOIN outputFormat selection parameter added --- server.js | 17 +++++++- utils/join-models.js | 100 ++++++++++++++++++++++++------------------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/server.js b/server.js index 9644000..925b833 100644 --- a/server.js +++ b/server.js @@ -94,8 +94,21 @@ app.use('/join', cors(), (req, res) => { acl: acl }; - let joinModels = new JOIN.JoinModelsJSON(context); - + // select the output format + let params = req.body; + let joinModels = {}; + + if(params.outputFormat === 'TEST'){ + joinModels = new JOIN.JoinModels(context); + }else if(params.outputFormat === 'CSV'){ + joinModels = new JOIN.JoinModelsCSV(context); + }else if(params.outputFormat === 'JSON'){ + joinModels = new JOIN.JoinModelsJSON(context); + }else{ + return res.status(415).send({error: "outputFormat = TEST/CSV/JSON is required"}); + } + + // start data transmission joinModels.run(req.body, res).then(() => { res.end(); }).catch(error => { diff --git a/utils/join-models.js b/utils/join-models.js index ed987a4..a34f751 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -25,49 +25,58 @@ let LinkedList = require('linked-list'); * The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. * Below goes an example of the currently supported parameter set: * - * [ { - * "name" : "individual", // Name of the model as it appears in the corresponding index.js + * { * + * outputFormat = CSV // (REQUIRED) For the moment can be TEST, CSV or JSON only, see + * // the corresponding module exports * - * "association_name" : "transcript_counts", - * // (REQUIRED) There can be more than one association between two models, - * // the way to differ between these associations is an "as_name" - * // used by sequelize. This name is used by codegen to create resolvers and - * // is used here to find them. * + * modelAdjacencies = // (REQUIRED) + * [ + * { + * "name" : "individual", // Name of the model as it appears in the corresponding index.js * * - * "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility - * // to filter out unnecessary columns of the table. However, is is easy - * // to implement this functionality inside a "constructRow" function. This way - * // it can be possible to create different cut-offs of the database at the - * // presentation level and resolve the data analysis problem at a low cost. - * "name", - * "createdAt" - * ], + * "association_name" : "transcript_counts", + * // (REQUIRED) There can be more than one association between two models, + * // the way to differ between these associations is an "as_name" + * // used by sequelize. This name is used by codegen to create resolvers and + * // is used here to find them. * - * <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the - * // JOIN chain. * * - * }, - * { - * "name" : "transcript_count", - * // The last element of the association chain does not require an "assoc" - * // structure, it has no sense here and will be ignored if present. + * "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility + * // to filter out unnecessary columns of the table. However, is is easy + * // to implement this functionality inside a "constructRow" function. This way + * // it can be possible to create different cut-offs of the database at the + * // presentation level and resolve the data analysis problem at a low cost. + * "name", + * "createdAt" + * ], + * + * <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the + * // JOIN chain. + * * - * "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or - * // "belongsToMany", there can be more than one "transcript_count" record - * "field" : "name", // associated with the same "individual". - * "value" : { // The "transcript_count" records can be filtered and ordered - * "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of - * }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and - * "operator" : "like" // "order" parameters will be ignored. * }, + * { + * "name" : "transcript_count", + * // The last element of the association chain does not require an "assoc" + * // structure, it has no sense here and will be ignored if present. * - * order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. + * "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or + * // "belongsToMany", there can be more than one "transcript_count" record + * "field" : "name", // associated with the same "individual". + * "value" : { // The "transcript_count" records can be filtered and ordered + * "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of + * }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and + * "operator" : "like" // "order" parameters will be ignored. + * }, + * + * order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. + * } + * ] * } - * ] * * PERMISSIONS * @@ -101,8 +110,9 @@ class JoinModels { * @returns {Promise} generally a void function */ - async run(modelAdjacencies, httpWritableStream) { + async run(params, httpWritableStream) { + let modelAdjacencies = params.modelAdjacencies; if ( ! modelAdjacencies || modelAdjacencies.length === 0) throw Error(`modelAdjacencies array is undefined`); @@ -129,9 +139,7 @@ class JoinModels { if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); // store raw names of the model attributes if not given at input - console.log("Starting"); let all_attributes = schema.getModelFieldByAnnotation(cur.model_adj.name,'@original-field'); - console.log(`All attributes for model: ${cur.model_adj.name} are >>> ${all_attributes}`); if( ! cur.model_adj.attributes ) { cur.model_adj.attributes = all_attributes; }else{ @@ -311,6 +319,8 @@ class JoinModels { cur.model_adj.data = null; }else{ cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); + if(!cur.model_adj.data) + cur.model_adj.data = null; } return cur; @@ -500,7 +510,7 @@ class JoinModelsJSON extends JoinModels{ }; }; - +//TODO: add "..." for each column data class JoinModelsCSV extends JoinModels{ /** * Internal class parameters @@ -541,10 +551,10 @@ class JoinModelsCSV extends JoinModels{ if(this.csv_header) header += cur.model_adj.name + "." + attr + ","; - if(cur.model_adj.data === null){ - str += "null,"; + if(cur.model_adj.data === null || cur.model_adj.data[attr] === null){ + str += `"NULL",`; }else{ - str += cur.model_adj.data[attr] + ","; + str += `"${cur.model_adj.data[attr]}"` + ","; } } @@ -573,17 +583,17 @@ module.exports.JoinModelsCSV = JoinModelsCSV; CURL tests (copy-paste to console): -curl -d '[ { "name" : "individual", "association_name" : "transcript_counts" }, { "name" : "transcript_count"} ]' -H "Content-Type: application/json" http://localhost:3000/join - +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "individual", "association_name" : "transcript_counts" }, { "name" : "transcript_count"} ]}' -H "Content-Type: application/json" http://localhost:3000/join -curl -d '[ { "name" : "transcript_count", "association_name" : "individual" }, { "name" : "individual"} ]' -H "Content-Type: application/json" http://localhost:3000/join +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "individual" }, { "name" : "individual"} ]}' -H "Content-Type: application/json" http://localhost:3000/join +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "individual", "attributes" : ["id"] }, { "name" : "individual", "attributes" : ["id"]} ]}' -H "Content-Type: application/json" http://localhost:3000/join -curl -d '[ { "name" : "transcript_count", "association_name" : "individual", "attributes" : ["id"] }, { "name" : "individual", "attributes" : ["id"]} ]' -H "Content-Type: application/json" http://localhost:3000/join +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]}' -H "Content-Type: application/json" http://localhost:3000/join -curl -d '[ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]' -H "Content-Type: application/json" http://localhost:3000/join +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "aminoacidsequence", "attributes" : [ "id", "gene"] } , {"name": "aminoacidsequence"} ]}' -H "Content-Type: application/json" http://localhost:3000/join -curl -d '[ { "name" : "transcript_count", "association_name" : "aminoacidsequence", "attributes" : [ "id", "gene"] } , {"name": "aminoacidsequence"} ]' -H "Content-Type: application/json" http://localhost:3000/join +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name": "aminoacidsequence", "attributes" : [ "id"], "association_name" : "transcript_counts"}, { "name" : "transcript_count" } ]}' -H "Content-Type: application/json" http://localhost:3000/join -*/ +*/ \ No newline at end of file From c32f6260cea0bbebfa06865066e3df3bb71909b3 Mon Sep 17 00:00:00 2001 From: YKolokoltsev Date: Mon, 22 Apr 2019 10:21:34 -0500 Subject: [PATCH 090/125] removed TODO --- utils/join-models.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/join-models.js b/utils/join-models.js index a34f751..721dd81 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -510,7 +510,7 @@ class JoinModelsJSON extends JoinModels{ }; }; -//TODO: add "..." for each column data + class JoinModelsCSV extends JoinModels{ /** * Internal class parameters From 21ba2dc5b285afce19596d1548ccb77df8413295 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 14 May 2019 17:26:06 -0500 Subject: [PATCH 091/125] dictionary for data types added --- utils/graphql-sequelize-types.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 utils/graphql-sequelize-types.js diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js new file mode 100644 index 0000000..9e7b3e5 --- /dev/null +++ b/utils/graphql-sequelize-types.js @@ -0,0 +1,11 @@ +/* +Data types dictionary from graphql-type to sequelize-type +*/ + +module.exports = { + + "Int" : 'INTEGER', + "String": 'TEXT', + "Float": 'FLOAT', + "Boolean": 'BOOLEAN' +} From 6889479872ad5feaa3cf89ba9566d9685a32d652 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 4 Jun 2019 01:48:16 -0500 Subject: [PATCH 092/125] sequelize-operators-added --- connection.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/connection.js b/connection.js index 331c6b8..99b2624 100644 --- a/connection.js +++ b/connection.js @@ -10,7 +10,12 @@ config.operatorsAliases = { $or: Op.or, $like: Op.like, $between: Op.between, - $in: Op.in + $in: Op.in, + $gt: Op.gt, + $gte: Op.gte, + $lt: Op.lt, + $lte: Op.lte, + $ne: Op.ne }; sequelize = new Sequelize(config); From 955075c1707952e2b1425cd2a2910995bbd86e07 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 17 Jun 2019 14:51:08 -0500 Subject: [PATCH 093/125] date types added to dictionary --- utils/graphql-sequelize-types.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js index 9e7b3e5..d10ab02 100644 --- a/utils/graphql-sequelize-types.js +++ b/utils/graphql-sequelize-types.js @@ -7,5 +7,8 @@ module.exports = { "Int" : 'INTEGER', "String": 'TEXT', "Float": 'FLOAT', - "Boolean": 'BOOLEAN' + "Boolean": 'BOOLEAN', + "Date": "DATEONLY", + "Time": "TIME", + "DateTime": "DATE" } From 2a8ded3229144297a42938a8a694ad2458be47b5 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 21 Jun 2019 17:10:48 -0500 Subject: [PATCH 094/125] index for models extended as class --- models_index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/models_index.js b/models_index.js index 974d55d..3b74783 100644 --- a/models_index.js +++ b/models_index.js @@ -1,8 +1,9 @@ const fs = require('fs'); const path = require('path'); +const Sequelize = require("sequelize"); sequelize = require('./connection'); -var models = {}; +let models = {}; // ********************************************************************************** @@ -15,8 +16,8 @@ fs.readdirSync("./models") }) .forEach(function(file) { console.log(file); - let model = sequelize['import'](path.join("./models", file)); - + let model_file = rquire(path.join("./models", file)); + let model = model_file.init(sequelize, Sequelize); let validator_patch = path.join('./validations', file); if(fs.existsSync(validator_patch)){ @@ -62,5 +63,3 @@ fs.readdirSync("./models-webservice") }); module.exports = models; - - From c974ac7b33cb941f0664bb7d94e9cc68c02c9a34 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 24 Jun 2019 08:14:54 -0500 Subject: [PATCH 095/125] models' index path --- models_index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/models_index.js b/models_index.js index 3b74783..611a98d 100644 --- a/models_index.js +++ b/models_index.js @@ -1,9 +1,8 @@ const fs = require('fs'); const path = require('path'); -const Sequelize = require("sequelize"); sequelize = require('./connection'); -let models = {}; +var models = {}; // ********************************************************************************** @@ -16,8 +15,8 @@ fs.readdirSync("./models") }) .forEach(function(file) { console.log(file); - let model_file = rquire(path.join("./models", file)); - let model = model_file.init(sequelize, Sequelize); + let model = equire(path.join(__dirname,'models', file)); + let validator_patch = path.join('./validations', file); if(fs.existsSync(validator_patch)){ From f3a3bff5b4e3eb5a53b5fab4d478b665d508a131 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 24 Jun 2019 08:25:14 -0500 Subject: [PATCH 096/125] typo in models' path fixed --- config/config.json | 6 +++--- models_index.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.json b/config/config.json index e33aaa7..b0b9c84 100644 --- a/config/config.json +++ b/config/config.json @@ -1,8 +1,8 @@ { "development": { - "username": "sciencedb", - "password": "sciencedb", - "database": "sciencedb_development", + "username": "test_code_gen", + "password": "test_code_gen", + "database": "test_code_gen", "host": "127.0.0.1", "dialect": "postgres", "operatorsAliases": false diff --git a/models_index.js b/models_index.js index 611a98d..fb5f7f4 100644 --- a/models_index.js +++ b/models_index.js @@ -15,7 +15,7 @@ fs.readdirSync("./models") }) .forEach(function(file) { console.log(file); - let model = equire(path.join(__dirname,'models', file)); + let model = require(path.join(__dirname,'models', file)); let validator_patch = path.join('./validations', file); From af5fa39e29e2d49efb5016808777ab9fd3111840 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 24 Jun 2019 09:07:41 -0500 Subject: [PATCH 097/125] models' index path --- models_index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models_index.js b/models_index.js index fb5f7f4..fe7a088 100644 --- a/models_index.js +++ b/models_index.js @@ -15,7 +15,8 @@ fs.readdirSync("./models") }) .forEach(function(file) { console.log(file); - let model = require(path.join(__dirname,'models', file)); + let model_file = require(path.join(__dirname,'models', file)); + let model = model_file.init(sequelize, Sequelize); let validator_patch = path.join('./validations', file); From c839ae29dd47b69437994f010ba79471bf887aac Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 24 Jun 2019 09:34:15 -0500 Subject: [PATCH 098/125] Sequelize required in models' index --- models_index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/models_index.js b/models_index.js index fe7a088..4bb4641 100644 --- a/models_index.js +++ b/models_index.js @@ -1,5 +1,6 @@ const fs = require('fs'); const path = require('path'); +const Sequelize = require('sequelize'); sequelize = require('./connection'); var models = {}; From 2645597d806190096b5cae9656732e0df2ff6292 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Fri, 28 Jun 2019 17:36:24 -0500 Subject: [PATCH 099/125] Fixed vulnerabilities and added Ajv --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 4deb97c..e2a4700 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "license": "ISC", "dependencies": { "acl": "^0.4.11", + "adm-zip": "^0.4.13", + "ajv": "^6.10.0", "awaitify-stream": "^1.0.2", "bcrypt": "^3.0.3", "bcryptjs": "^2.4.3", @@ -24,23 +26,21 @@ "faker": "^4.1.0", "graphql": "^0.13.1", "jsonwebtoken": "^8.2.0", + "linked-list": "^2.0.0", "lodash": "^4.17.5", "mathjs": "^5.2.0", "merge-graphql-schemas": "^1.5.1", "mysql2": "^1.6.4", + "nodemailer": "^5.1.1", + "nodemailer-smtp-transport": "^2.7.4", "pg": "^7.4.1", "pg-hstore": "^2.3.2", - "sequelize": "^4.35.2", - "sequelize-cli": "^4.0.0", + "sequelize": "^6.0.0", + "sequelize-cli": "^5.5.0", "supertest": "^3.1.0", - "uuidv4": "^2.0.0", - "xlsx": "^0.12.11", "sync-request": "^6.0.0", - "nodemailer" : "^5.1.1", - "nodemailer-smtp-transport": "^2.7.4", - "joi" : "^14.3.1", - "linked-list": "^2.0.0", - "adm-zip": "^0.4.13" + "uuidv4": "^2.0.0", + "xlsx": "^0.12.11" }, "devDependencies": { "mocha": "^5.2.0", From e325a74e9fad853e9bb47b619ab861d08c89a3c3 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Fri, 28 Jun 2019 17:57:31 -0500 Subject: [PATCH 100/125] Adjusted to async validation --- utils/validatorUtil.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 8df9caf..1ed8abc 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -6,13 +6,14 @@ * @param {string} validatorFunction Name of the validator function * @param {object} dataModel The empty data model object * @param {object} data JSON data to be inserted into the dataModel - * @return {Error} An error object if data is invalid or null otherwise + * @return {Promise} The result of invoking the respective validator, or + * undefined if no validator was found to be registered * */ -module.exports.ifHasValidatorFunctionInvoke = function( validatorFunction, dataModel, data) { +module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { - return dataModel.prototype[validatorFunction](data).error; + return await dataModel.prototype[validatorFunction](data); } }; From 2f34fd1fe41689508b03ce5c79be0545f211a6d4 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 2 Jul 2019 22:50:08 -0500 Subject: [PATCH 101/125] sequelize working with operators until new fix --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e2a4700..98df09b 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "nodemailer-smtp-transport": "^2.7.4", "pg": "^7.4.1", "pg-hstore": "^2.3.2", - "sequelize": "^6.0.0", - "sequelize-cli": "^5.5.0", + "sequelize": "^4.35.2", + "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", "sync-request": "^6.0.0", "uuidv4": "^2.0.0", From c5473c7edca98720829000cc6a09a838873c2633 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 4 Jul 2019 12:03:09 -0500 Subject: [PATCH 102/125] new validation sintax in file tools --- utils/file-tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index 3b88abf..d4e714d 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -147,7 +147,7 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { record = exports.replacePojoNullValueWithLiteralNull(record); console.log(record); - let error = validatorUtil.ifHasValidatorFunctionInvoke('validatorForCreate', model, record); + let error = validatorUtil.ifHasValidatorFunctionInvoke('validateForCreate', model, record); if (!!error) { From 0c41ccf85f112c7ed60e70290950b2a6e4c17c6d Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 4 Jul 2019 15:03:50 -0500 Subject: [PATCH 103/125] catch error from new validation --- utils/file-tools.js | 17 ++++++++--------- utils/validatorUtil.js | 5 ++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index d4e714d..745f233 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -147,16 +147,10 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { record = exports.replacePojoNullValueWithLiteralNull(record); console.log(record); - let error = validatorUtil.ifHasValidatorFunctionInvoke('validateForCreate', model, record); - - if (!!error) { - - console.log(`Validation error during CSV batch upload: ${JSON.stringify(error)}`); - error.record = record; - errors.push(error); - - } else { + try{ + let result = await validatorUtil.ifHasValidatorFunctionInvoke('validateForCreate', model, record); + console.log(result); await model.create(record, { transaction: transaction }).then(created => { @@ -170,8 +164,13 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { error.record = record; errors.push(error); }) + }catch(error){ + console.log(`Validation error during CSV batch upload: ${JSON.stringify(error.message)}`); + error['record'] = record; + errors.push(error); } + } // close the addedRecords file so it can be sent afterwards diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 1ed8abc..9d4af89 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -13,7 +13,10 @@ module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { + try{ return await dataModel.prototype[validatorFunction](data); + }catch( err) { + throw err; + } } }; - From 31ee4cc65f0c536cc457bcaa5522b82ad5f44eea Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 18 Jul 2019 13:47:00 -0500 Subject: [PATCH 104/125] index model used within the models --- models_index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/models_index.js b/models_index.js index 4bb4641..fbb8d51 100644 --- a/models_index.js +++ b/models_index.js @@ -4,7 +4,7 @@ const Sequelize = require('sequelize'); sequelize = require('./connection'); var models = {}; - +module.exports = models; // ********************************************************************************** // IMPORT SEQUEILIZE MODELS @@ -62,5 +62,3 @@ fs.readdirSync("./models-webservice") models[model.name] = model; }); - -module.exports = models; From 8ec18742ee68d99685a44af880937df72091e765 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 19 Jul 2019 11:16:15 -0500 Subject: [PATCH 105/125] search param considered in join models util --- utils/join-models.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/join-models.js b/utils/join-models.js index 721dd81..8c2cd8d 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -318,7 +318,7 @@ class JoinModels { if(cur.model_adj.search_params.pagination.offset > 0){ cur.model_adj.data = null; }else{ - cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); + cur.model_adj.data = await cur.prev.model_adj.data[as_name]({},context); if(!cur.model_adj.data) cur.model_adj.data = null; } @@ -596,4 +596,4 @@ curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name": "aminoacidsequence", "attributes" : [ "id"], "association_name" : "transcript_counts"}, { "name" : "transcript_count" } ]}' -H "Content-Type: application/json" http://localhost:3000/join -*/ \ No newline at end of file +*/ From cdd0299992646e6a30141a0cdf00f1ccdb502654 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 12 Aug 2019 18:03:34 -0500 Subject: [PATCH 106/125] missing comma in dictionary --- utils/graphql-sequelize-types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js index ad3e2ec..d10ab02 100644 --- a/utils/graphql-sequelize-types.js +++ b/utils/graphql-sequelize-types.js @@ -5,7 +5,7 @@ Data types dictionary from graphql-type to sequelize-type module.exports = { "Int" : 'INTEGER', - "String": 'TEXT' + "String": 'TEXT', "Float": 'FLOAT', "Boolean": 'BOOLEAN', "Date": "DATEONLY", From 7b7cdc200820df98fd46778cfa799aebcb3a59cc Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 19 Aug 2019 19:40:38 -0500 Subject: [PATCH 107/125] Merged async-validation with master --- package.json | 1 + tmp.txt | 6 ++++++ utils/file-tools.js | 17 ++++++++--------- utils/join-models.js | 4 ++-- utils/validatorUtil.js | 12 ++++++++---- 5 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 tmp.txt diff --git a/package.json b/package.json index 5373f05..8f13221 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "dependencies": { "acl": "^0.4.11", + "ajv": "^6.10.0", "awaitify-stream": "^1.0.2", "bcrypt": "^3.0.3", "bcryptjs": "^2.4.3", diff --git a/tmp.txt b/tmp.txt new file mode 100644 index 0000000..607a4e3 --- /dev/null +++ b/tmp.txt @@ -0,0 +1,6 @@ +models_index.js +package.json +server.js +utils/file-tools.js +utils/join-models.js +utils/validatorUtil.js diff --git a/utils/file-tools.js b/utils/file-tools.js index 3b88abf..745f233 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -147,16 +147,10 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { record = exports.replacePojoNullValueWithLiteralNull(record); console.log(record); - let error = validatorUtil.ifHasValidatorFunctionInvoke('validatorForCreate', model, record); - - if (!!error) { - - console.log(`Validation error during CSV batch upload: ${JSON.stringify(error)}`); - error.record = record; - errors.push(error); - - } else { + try{ + let result = await validatorUtil.ifHasValidatorFunctionInvoke('validateForCreate', model, record); + console.log(result); await model.create(record, { transaction: transaction }).then(created => { @@ -170,8 +164,13 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { error.record = record; errors.push(error); }) + }catch(error){ + console.log(`Validation error during CSV batch upload: ${JSON.stringify(error.message)}`); + error['record'] = record; + errors.push(error); } + } // close the addedRecords file so it can be sent afterwards diff --git a/utils/join-models.js b/utils/join-models.js index 721dd81..8c2cd8d 100644 --- a/utils/join-models.js +++ b/utils/join-models.js @@ -318,7 +318,7 @@ class JoinModels { if(cur.model_adj.search_params.pagination.offset > 0){ cur.model_adj.data = null; }else{ - cur.model_adj.data = await cur.prev.model_adj.data[as_name]("",context); + cur.model_adj.data = await cur.prev.model_adj.data[as_name]({},context); if(!cur.model_adj.data) cur.model_adj.data = null; } @@ -596,4 +596,4 @@ curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name": "aminoacidsequence", "attributes" : [ "id"], "association_name" : "transcript_counts"}, { "name" : "transcript_count" } ]}' -H "Content-Type: application/json" http://localhost:3000/join -*/ \ No newline at end of file +*/ diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 8df9caf..9d4af89 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -6,13 +6,17 @@ * @param {string} validatorFunction Name of the validator function * @param {object} dataModel The empty data model object * @param {object} data JSON data to be inserted into the dataModel - * @return {Error} An error object if data is invalid or null otherwise + * @return {Promise} The result of invoking the respective validator, or + * undefined if no validator was found to be registered * */ -module.exports.ifHasValidatorFunctionInvoke = function( validatorFunction, dataModel, data) { +module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { - return dataModel.prototype[validatorFunction](data).error; + try{ + return await dataModel.prototype[validatorFunction](data); + }catch( err) { + throw err; + } } }; - From 0d200e0631e77e350fea554e2cc75e98dea2c914 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 19 Aug 2019 19:48:50 -0500 Subject: [PATCH 108/125] Switched from sync Joi to async AJV validator --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 8f13221..f73fbf2 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "faker": "^4.1.0", "graphql": "^0.13.1", "graphql-iso-date": "^3.6.1", - "joi": "^14.3.1", "jsonwebtoken": "^8.2.0", "linked-list": "^2.0.0", "lodash": "^4.17.5", From ba02caa0771743d69b72a542d78e5b11a82a319b Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Mon, 19 Aug 2019 19:58:09 -0500 Subject: [PATCH 109/125] Removed unwanted file tmp.txt --- tmp.txt | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 tmp.txt diff --git a/tmp.txt b/tmp.txt deleted file mode 100644 index 607a4e3..0000000 --- a/tmp.txt +++ /dev/null @@ -1,6 +0,0 @@ -models_index.js -package.json -server.js -utils/file-tools.js -utils/join-models.js -utils/validatorUtil.js From 11a0fc6a68ac09985961693bd56f80fc34d04cf2 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 20 Aug 2019 18:03:35 -0500 Subject: [PATCH 110/125] Added util function to define AJV ISO Date-Time keywords. --- utils/validatorUtil.js | 86 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 9d4af89..79594bf 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -1,3 +1,4 @@ +const gd = require('graphql-iso-date') /** * ifHasValidatorFunctionInvoke - checks if data model has a validator function with @@ -10,7 +11,6 @@ * undefined if no validator was found to be registered * */ - module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, dataModel, data) { if (typeof dataModel.prototype[validatorFunction] === "function") { try{ @@ -20,3 +20,87 @@ module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, } } }; + +/** + * Adds AJV asynchronous keywords to the argument AJV instance that define ISO + * Date, ISO Time, and ISO DateTime strings or the respective GraphQL instances + * (see package 'graphql-iso-date'). Use in a schema as in the following + * example: let schema = { '$async': true, properties: { startDate: { isoDate: + * true } } } + * + * @param {object} ajv - An instance of AJV (see package 'ajv' for details. + * + * @return {object} the modified instance of ajv. As Javascript uses references + * this return value can be ignored, as long as the original argument is kept + * and used. + */ +module.exports.addDateTimeAjvKeywords = function(ajv) { + ajv.addKeyword('isoDate', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLDate.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoDate', + message: 'Must be a GraphQLDate instance or a ISO Date formatted string (e.g. "1900-12-31").', + params: { + 'keyword': 'isoDate' + } + }])) + }) + } + } + }, + errors: true + }) + + ajv.addKeyword('isoTime', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLTime.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoTime', + message: 'Must be a GraphQLTime instance or a ISO Time formatted string (e.g. "13:56:45Z" or "13.56.45.1982Z").', + params: { + 'keyword': 'isoTime' + } + }])) + }) + } + } + }, + errors: true + }) + + ajv.addKeyword('isoDateTime', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLTime.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoDateTime', + message: 'Must be a GraphQLDateTime instance or a ISO Date-Time formatted string (e.g. "1900-12-31T23:59:59Z" or "1900-12-31T23:59:59.1982Z").', + params: { + 'keyword': 'isoDateTime' + } + }])) + }) + } + } + }, + errors: true + }) +} From 02f9eacce1d507af63cb6f768de09f39e3189a09 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 20 Aug 2019 18:17:57 -0500 Subject: [PATCH 111/125] Debugged return value of 'addDateTimeAjvKeywords' --- utils/validatorUtil.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index 79594bf..c79749e 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -103,4 +103,6 @@ module.exports.addDateTimeAjvKeywords = function(ajv) { }, errors: true }) + + return ajv } From fe87c3210c25d4d6664b8d24f8fd3ecbc843719a Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Tue, 20 Aug 2019 18:24:36 -0500 Subject: [PATCH 112/125] Debugged missing require('ajv') --- utils/validatorUtil.js | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js index c79749e..4ca5f72 100644 --- a/utils/validatorUtil.js +++ b/utils/validatorUtil.js @@ -1,4 +1,5 @@ const gd = require('graphql-iso-date') +const Ajv = require('ajv') /** * ifHasValidatorFunctionInvoke - checks if data model has a validator function with From f657e1a6aa6f50427e18c6fac197a7d7ab9f311b Mon Sep 17 00:00:00 2001 From: "Dr. Asis hallab" Date: Thu, 5 Sep 2019 14:55:45 +0200 Subject: [PATCH 113/125] Debugged bulkCreate from streamed CSV. --- utils/file-tools.js | 127 ++++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/utils/file-tools.js b/utils/file-tools.js index 745f233..b34c313 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -70,11 +70,11 @@ exports.parseXlsx = function(bstr) { * * @param {String} path - A path to the file */ -exports.deleteIfExists = function (path){ - console.log(`Removing ${path}`); - fs.unlink(path, function (err){ - // file may be already deleted - }); +exports.deleteIfExists = function(path) { + console.log(`Removing ${path}`); + fs.unlink(path, function(err) { + // file may be already deleted + }); }; /** @@ -83,31 +83,30 @@ exports.deleteIfExists = function (path){ * @param {Object} pojo - A plain old JavaScript object. * * @return {Object} A modified clone of the argument pojo in which all String - * "NULL" or "null" values are replaced with literal nulls. + * "NULL" or "null" values are deleted. */ exports.replacePojoNullValueWithLiteralNull = function(pojo) { - if (pojo === null) { - return null + if (pojo === null || pojo === undefined) { + return null + } + let res = Object.assign({}, pojo); + Object.keys(res).forEach((k) => { + if (typeof res[k] === "string" && res[k].match(/\s*null\s*/i)) { + delete res[k]; } - let res = Object.assign({}, pojo); - Object.keys(res).forEach((k) => { - if(res[k].localeCompare("NULL", undefined, { sensitivity: 'accent' }) === 0){ - //res[k] = null - delete res[k]; - } - }); - return res + }); + return res }; /** -* Parse by streaming a csv file and create the records in the correspondant table -* @function -* @param {string} csvFilePath - The path where the csv file is stored. -* @param {object} model - Sequelize model, record will be created through this model. -* @param {string} delim - Set the field delimiter in the csv file. One or multiple character. -* @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). -*/ + * Parse by streaming a csv file and create the records in the correspondant table + * @function + * @param {string} csvFilePath - The path where the csv file is stored. + * @param {object} model - Sequelize model, record will be created through this model. + * @param {string} delim - Set the field delimiter in the csv file. One or multiple character. + * @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). + */ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { if (!delim) delim = ","; @@ -116,8 +115,10 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { // Wrap all database actions within a transaction: let transaction = await model.sequelize.transaction(); - let addedFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + ".json"; - let addedZipFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + ".zip"; + let addedFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + + ".json"; + let addedZipFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + + ".zip"; console.log(addedFilePath); console.log(addedZipFilePath); @@ -128,14 +129,15 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { fs.createReadStream(csvFilePath).pipe( csv_parse({ delimiter: delim, - columns: cols + columns: cols, + cast: true }) ) ); // Create an output file stream let addedRecords = awaitifyStream.createWriter( - fs.createWriteStream(addedFilePath) + fs.createWriteStream(addedFilePath) ); let record; @@ -143,48 +145,59 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { while (null !== (record = await csvStream.readAsync())) { - console.log(record); - record = exports.replacePojoNullValueWithLiteralNull(record); - console.log(record); - - - try{ - let result = await validatorUtil.ifHasValidatorFunctionInvoke('validateForCreate', model, record); - console.log(result); - await model.create(record, { - transaction: transaction - }).then(created => { - - // this is async, here we just push new line into the parallel thread - // synchronization goes at endAsync; - addedRecords.writeAsync(`${JSON.stringify(created)}\n`); - - }).catch(error => { - console.log(`Caught sequelize error during CSV batch upload: ${JSON.stringify(error)}`); - error.record = record; - errors.push(error); - }) - }catch(error){ - console.log(`Validation error during CSV batch upload: ${JSON.stringify(error.message)}`); - error['record'] = record; + console.log(record); + record = exports.replacePojoNullValueWithLiteralNull(record); + console.log(record); + + + try { + let result = await validatorUtil.ifHasValidatorFunctionInvoke( + 'validateForCreate', model, record); + console.log(result); + await model.create(record, { + transaction: transaction + }).then(created => { + + // this is async, here we just push new line into the parallel thread + // synchronization goes at endAsync; + addedRecords.writeAsync(`${JSON.stringify(created)}\n`); + + }).catch(error => { + console.log( + `Caught sequelize error during CSV batch upload: ${JSON.stringify(error)}` + ); + error.record = record; errors.push(error); + }) + } catch (error) { + console.log( + `Validation error during CSV batch upload: ${JSON.stringify(error)}` + ); + error['record'] = record; + errors.push(error); - } + } } // close the addedRecords file so it can be sent afterwards await addedRecords.endAsync(); - if(errors.length > 0) { - let message = "Some records could not be submitted. No database changes has been applied.\n"; + if (errors.length > 0) { + let message = + "Some records could not be submitted. No database changes has been applied.\n"; message += "Please see the next list for details:\n"; errors.forEach(function(error) { - message += `record ${JSON.stringify(error.record)} ${error.message}; \n`; + valErrMessages = error.errors.reduce((acc, val) => { + return acc.concat(val.dataPath).concat(" ").concat(val.message) + .concat(" ") + }) + message += + `record ${JSON.stringify(error.record)} ${error.message}: ${valErrMessages}; \n`; }); - throw new Error(message.slice(0, message.length-1)); + throw new Error(message.slice(0, message.length - 1)); } await transaction.commit(); @@ -210,6 +223,6 @@ exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { throw error; } finally { - exports.deleteIfExists(addedFilePath); + exports.deleteIfExists(addedFilePath); } }; From 1428e9f78abb7e25667de9e4ed111f23e484cea5 Mon Sep 17 00:00:00 2001 From: "Dr. Asis Hallab" Date: Thu, 19 Sep 2019 14:37:09 +0200 Subject: [PATCH 114/125] Server nows expects signed in user JWT is expected to be present in the header and to be valid. Except on route '/login'. --- server.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 1e88e22..40c0ed4 100644 --- a/server.js +++ b/server.js @@ -38,6 +38,9 @@ next(); }); + // Force users to sign in to get access to anything else than '/login' + app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); + /* Temporary solution: acl rules set */ if (process.argv.length > 2 && process.argv[2] == 'acl') { var node_acl = require('acl'); @@ -50,7 +53,6 @@ acl.allow(aclRules); console.log("Authoization rules set!"); - app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); } else { console.log("Open server, no authorization rules"); } From 50f598e9fd380f74aac019377cbd235c710c60de Mon Sep 17 00:00:00 2001 From: vsuaste Date: Mon, 23 Sep 2019 19:09:32 -0500 Subject: [PATCH 115/125] simple export route added --- server.js | 25 ++++++++++++++++++++++++ utils/simple-export.js | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 utils/simple-export.js diff --git a/server.js b/server.js index 40c0ed4..b9b5a9f 100644 --- a/server.js +++ b/server.js @@ -130,7 +130,32 @@ app.use('/join', cors(), (req, res) => { +app.use('/export', cors(), (req, res) =>{ + + // check if the Content-Type is in JSON so that bodyParser can be applied automatically + if (!req.is('application/json')) + return res.status(415).send({error: "JSON Content-Type expected"}); + + let context = { + request: req, + acl : acl + } + + let body_info = req.body; + + simpleExport(context, body_info ,res).then( () =>{ + res.end(); + }).catch( error => { + let formattedError = { + message: error.message, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", + path: error.path + }; + res.status(500).send(formattedError); + }); + +}); diff --git a/utils/simple-export.js b/utils/simple-export.js new file mode 100644 index 0000000..0bbff54 --- /dev/null +++ b/utils/simple-export.js @@ -0,0 +1,44 @@ + +const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); +const inflection = require('inflection'); + +module.exports = async function(context, body_info, writableStream ){ + //get resolver name for model + let model_name = body_info.model; + let getter_resolver = inflection.pluralize(model_name.slice(0,1).toLowerCase() + model_name.slice(1, model_name.length)); + + let batch_step = { + limit: 2, + offset: 0 + } + + let count_resolver = 'count'+inflection.pluralize(model_name.slice(0,1).toUpperCase() + model_name.slice(1, model_name.length)); + let total_records = resolvers[count_resolver](_, context); + console.log("TOTAL NUMBER OF RECORDS TO STREAM: ", total_records); + + // http send stream header + let timestamp = new Date().getTime(); + httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', + 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + + while(batch_step.offset <= total_records){ + + try{ + await data = resolvers[getter_resolver]({pagination: batch_step}); + await writableStream.write(data); + batch_step.offset = batch_step.offset + limit; + }catch(err){ + /* + We can't throw an error to Express server at this stage because the response Content-Type + was already sent. So we can try to attach it to the end of file. + */ + console.log(err); + await writableStream.write(`{error : ${err.message}}\n`); + return; + } + + } + + + +} From 0c93bb9748e74ac2385873facbddaaff7c00fed1 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 24 Sep 2019 00:23:20 -0500 Subject: [PATCH 116/125] simple export function tested --- server.js | 1 + utils/simple-export.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index b9b5a9f..4d8c244 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,7 @@ const bodyParser = require('body-parser'); const globals = require('./config/globals'); const JOIN = require('./utils/join-models'); + const simpleExport = require('./utils/simple-export'); const {GraphQLDateTime, GraphQLDate, GraphQLTime } = require('graphql-iso-date'); var { diff --git a/utils/simple-export.js b/utils/simple-export.js index 0bbff54..e37ffe3 100644 --- a/utils/simple-export.js +++ b/utils/simple-export.js @@ -1,4 +1,4 @@ - +const path = require('path'); const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); const inflection = require('inflection'); @@ -18,15 +18,15 @@ module.exports = async function(context, body_info, writableStream ){ // http send stream header let timestamp = new Date().getTime(); - httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', + writableStream.writeHead(200, {'Content-Type': 'application/force-download', 'Content-disposition': `attachment; filename = ${timestamp}.json`}); while(batch_step.offset <= total_records){ try{ - await data = resolvers[getter_resolver]({pagination: batch_step}); + await data = await resolvers[getter_resolver]({pagination: batch_step}, context); await writableStream.write(data); - batch_step.offset = batch_step.offset + limit; + batch_step.offset = batch_step.offset + batch_step.limit; }catch(err){ /* We can't throw an error to Express server at this stage because the response Content-Type From 33f853a98c7e61474ed619daebf72ab741113c7a Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 24 Sep 2019 12:04:18 -0500 Subject: [PATCH 117/125] evironmental variable REQUIRE_SIGN_IN --- config/globals.js | 1 + server.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/globals.js b/config/globals.js index 1139f7f..7255093 100644 --- a/config/globals.js +++ b/config/globals.js @@ -2,6 +2,7 @@ module.exports = { LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, PORT : process.env.PORT || 3000, ALLOW_ORIGIN: process.env.ALLOW_ORIGIN || "http://localhost:8080", + REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || false, MAIL_SERVICE: "gmail", MAIL_HOST: "smtp.gmail.com", MAIL_ACCOUNT: "sci.db.service@gmail.com", diff --git a/server.js b/server.js index 40c0ed4..96c445d 100644 --- a/server.js +++ b/server.js @@ -39,7 +39,11 @@ }); // Force users to sign in to get access to anything else than '/login' - app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); + // + if(globals.REQUIRE_SIGN_IN){ + app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); + } + /* Temporary solution: acl rules set */ if (process.argv.length > 2 && process.argv[2] == 'acl') { From 339d440f78019f1aa01aa2c1bd747f21042527d4 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Tue, 24 Sep 2019 12:10:20 -0500 Subject: [PATCH 118/125] default REQUIRE_SIGN_IN to true --- config/globals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/globals.js b/config/globals.js index 7255093..f585178 100644 --- a/config/globals.js +++ b/config/globals.js @@ -2,7 +2,7 @@ module.exports = { LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, PORT : process.env.PORT || 3000, ALLOW_ORIGIN: process.env.ALLOW_ORIGIN || "http://localhost:8080", - REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || false, + REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || true, MAIL_SERVICE: "gmail", MAIL_HOST: "smtp.gmail.com", MAIL_ACCOUNT: "sci.db.service@gmail.com", From 988bdf0aefb020cfcd123efaceea9902345b7357 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 25 Sep 2019 10:06:54 -0500 Subject: [PATCH 119/125] test require_sign_in variable --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 96c445d..aec8d75 100644 --- a/server.js +++ b/server.js @@ -18,7 +18,7 @@ var cors = require('cors'); - /* Server */ + /* Server */ const APP_PORT = globals.PORT; const app = express(); @@ -39,7 +39,7 @@ }); // Force users to sign in to get access to anything else than '/login' - // + console.log("REQUIRE: ",globals.REQUIRE_SIGN_IN); if(globals.REQUIRE_SIGN_IN){ app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); } From e343c15b11797828c70dc325b35dad5c2292d885 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 25 Sep 2019 10:18:44 -0500 Subject: [PATCH 120/125] require_sign_in variable as string --- config/globals.js | 2 +- server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/globals.js b/config/globals.js index f585178..4fcb596 100644 --- a/config/globals.js +++ b/config/globals.js @@ -2,7 +2,7 @@ module.exports = { LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, PORT : process.env.PORT || 3000, ALLOW_ORIGIN: process.env.ALLOW_ORIGIN || "http://localhost:8080", - REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || true, + REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || "true", MAIL_SERVICE: "gmail", MAIL_HOST: "smtp.gmail.com", MAIL_ACCOUNT: "sci.db.service@gmail.com", diff --git a/server.js b/server.js index aec8d75..3f015e8 100644 --- a/server.js +++ b/server.js @@ -40,7 +40,7 @@ // Force users to sign in to get access to anything else than '/login' console.log("REQUIRE: ",globals.REQUIRE_SIGN_IN); - if(globals.REQUIRE_SIGN_IN){ + if(globals.REQUIRE_SIGN_IN === "true"){ app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); } From f797459b756dcec8b4b2b0a5aa0aad231fcc67e8 Mon Sep 17 00:00:00 2001 From: Alicia Mastretta-Yanes Date: Wed, 25 Sep 2019 10:38:26 -0500 Subject: [PATCH 121/125] Update LICENSE --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 6b46aa843c1912cee7c57d81af1864758e5d351c Mon Sep 17 00:00:00 2001 From: vsuaste Date: Wed, 25 Sep 2019 16:54:10 -0500 Subject: [PATCH 122/125] csv stream export functionality --- utils/simple-export.js | 73 +++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/utils/simple-export.js b/utils/simple-export.js index e37ffe3..b630d16 100644 --- a/utils/simple-export.js +++ b/utils/simple-export.js @@ -1,31 +1,85 @@ const path = require('path'); const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); const inflection = require('inflection'); +const schema = require('./graphql_schema'); + +getAttributes = function( model_name ){ + return schema.getModelFieldByAnnotation(model_name, '@original-field'); +} + +crateHeaderCSV = function(attributes){ + let str_header = ""; + attributes.forEach( att =>{ + str_header+= att+","; + } ) + str_header= str_header.replace(/.$/,"\n"); + + return str_header; +} + +asyncForEach = async function(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +jsonToCSV = function(row_data, attributes){ + let str_csv = ""; + attributes.forEach( att => { + if(row_data[att]===null || row_data[att] === undefined){ + str_csv+='NULL.'; + }else { + str_csv+= row_data[att]+","; + } + }) + + str_csv= str_csv.replace(/.$/,"\n"); + return str_csv; +} + +// wait ms milliseconds +function wait(ms) { + return new Promise(r => setTimeout(r, ms)); +} + module.exports = async function(context, body_info, writableStream ){ //get resolver name for model let model_name = body_info.model; let getter_resolver = inflection.pluralize(model_name.slice(0,1).toLowerCase() + model_name.slice(1, model_name.length)); + //get count resolver + let count_resolver = 'count'+inflection.pluralize(model_name.slice(0,1).toUpperCase() + model_name.slice(1, model_name.length)); + let total_records = await resolvers[count_resolver]({}, context); + console.log("TOTAL NUMBER OF RECORDS TO STREAM: ", total_records); + + //pagination let batch_step = { - limit: 2, + limit: 1, offset: 0 } - let count_resolver = 'count'+inflection.pluralize(model_name.slice(0,1).toUpperCase() + model_name.slice(1, model_name.length)); - let total_records = resolvers[count_resolver](_, context); - console.log("TOTAL NUMBER OF RECORDS TO STREAM: ", total_records); - // http send stream header let timestamp = new Date().getTime(); writableStream.writeHead(200, {'Content-Type': 'application/force-download', 'Content-disposition': `attachment; filename = ${timestamp}.json`}); - while(batch_step.offset <= total_records){ + //get attributes names + let attributes = getAttributes(model_name); + + //write csv header + let csv_header = crateHeaderCSV(attributes); + await writableStream.write(csv_header); + + while(batch_step.offset < total_records){ try{ - await data = await resolvers[getter_resolver]({pagination: batch_step}, context); - await writableStream.write(data); + data = await resolvers[getter_resolver]({pagination: batch_step},context); + + await asyncForEach(data, async (record) =>{ + let row = jsonToCSV(record.dataValues, attributes); + await writableStream.write(row); + }) batch_step.offset = batch_step.offset + batch_step.limit; }catch(err){ /* @@ -38,7 +92,4 @@ module.exports = async function(context, body_info, writableStream ){ } } - - - } From d5aa51dc6b401fabefe7d45f64e02b32d75d9c02 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Thu, 26 Sep 2019 18:10:43 -0500 Subject: [PATCH 123/125] form request parsing --- server.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server.js b/server.js index cc02924..0620fdb 100644 --- a/server.js +++ b/server.js @@ -137,16 +137,12 @@ app.use('/join', cors(), (req, res) => { app.use('/export', cors(), (req, res) =>{ - // check if the Content-Type is in JSON so that bodyParser can be applied automatically - if (!req.is('application/json')) - return res.status(415).send({error: "JSON Content-Type expected"}); - let context = { request: req, acl : acl } - let body_info = req.body; + let body_info = req.query; simpleExport(context, body_info ,res).then( () =>{ res.end(); From 1ad2398b54b8632b709215c160d633139a5bc654 Mon Sep 17 00:00:00 2001 From: vsuaste Date: Fri, 27 Sep 2019 11:45:43 -0500 Subject: [PATCH 124/125] csv extension for export file --- utils/simple-export.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/simple-export.js b/utils/simple-export.js index b630d16..ce575e7 100644 --- a/utils/simple-export.js +++ b/utils/simple-export.js @@ -62,7 +62,7 @@ module.exports = async function(context, body_info, writableStream ){ // http send stream header let timestamp = new Date().getTime(); writableStream.writeHead(200, {'Content-Type': 'application/force-download', - 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + 'Content-disposition': `attachment; filename = ${timestamp}.csv`}); //get attributes names let attributes = getAttributes(model_name); From 8215cffcd648f20b93394e79b7481d38744b580c Mon Sep 17 00:00:00 2001 From: Francisco Ramirez Date: Wed, 16 Oct 2019 16:16:06 -0500 Subject: [PATCH 125/125] added error log redirection --- migrateDbAndStartServer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh index 1ccc6cf..5d58e45 100755 --- a/migrateDbAndStartServer.sh +++ b/migrateDbAndStartServer.sh @@ -27,4 +27,4 @@ if [ -d ./seeders ]; then fi # Start GraphQL-server -npm start # acl +npm start 2> /usr/src/app/error_server.log # acl