From a9225e44d7d6e298246d0ddc2fbc6e81b2614216 Mon Sep 17 00:00:00 2001 From: Mario Costa Date: Mon, 7 Feb 2022 22:32:50 -0300 Subject: [PATCH 1/2] refactor: npm audit fix, upgrade mongodb to v4, log4js to 6 and set minimum node to 12 --- .eslintrc.json | 2 +- package.json | 13 +++-- src/crud/mapper.js | 89 +++++++++++++++++--------------- src/crud/mongodb-query-filter.js | 26 ++++++---- 4 files changed, 72 insertions(+), 58 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index a3195e4..11ea60e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ }, "rules": { "require-jsdoc": "off", - "no-underscore-dangle": ["error", { "allow": ["_id"] }], + "no-underscore-dangle": ["error", { "allow": ["_id", "_count", "_pageSize", "_total_items", "_page_count"] }], "max-len": ["error", { "code": 120 }] }, "globals": { diff --git a/package.json b/package.json index 3d76611..10ad80f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "author": "Leandro Silva ", "license": "MIT", "private": false, + "engines": { + "node": ">=12.21" + }, "publishConfig": { "access": "public" }, @@ -25,19 +28,19 @@ "koa": "2.4.1", "koa-helmet": "3.3.0", "lodash.has": "^4.5.2", - "log4js": "2.4.1", + "log4js": "^6.4.1", "moment": "^2.22.2", - "mongodb": "2.2.34", + "mongodb": "^4.3.1", "query-string": "5.0.1", "uuid": "^8.3.0" }, "devDependencies": { "@babel/core": "^7.12.3", "@babel/preset-env": "^7.12.1", - "@commitlint/cli": "^8.1.0", - "@commitlint/config-conventional": "^8.1.0", + "@commitlint/cli": "^16.1.0", + "@commitlint/config-conventional": "^16.0.0", "babel-jest": "^26.6.1", - "eslint": "^6.2.1", + "eslint": "^7.32.0", "eslint-config-airbnb-base": "^14.0.0", "eslint-plugin-import": "^2.18.2", "husky": "^3.0.4", diff --git a/src/crud/mapper.js b/src/crud/mapper.js index 6702aa2..b72d25e 100644 --- a/src/crud/mapper.js +++ b/src/crud/mapper.js @@ -11,6 +11,25 @@ const DuplicationException = require('./duplication-exception'); const Uuid = require('../infra/uuid'); const MongoQF = require('./mongodb-query-filter'); +function createQueryFilter(schema) { + if (Object.prototype.hasOwnProperty.call(schema, 'searchable') === false) { + // eslint-disable-next-line no-param-reassign + schema.searchable = Object.keys(schema.properties); + } + const whitelist = { after: 1, before: 1, between: 1 }; + schema.searchable.forEach((key) => { + whitelist[key] = 1; + }); + return new MongoQF({ + custom: { + between: 'updatedAt', + after: 'updatedAt', + before: 'updatedAt', + }, + whitelist, + }); +} + class CrudMapper { constructor(db, schema, options = {}) { this.schema = schema; @@ -21,7 +40,7 @@ class CrudMapper { this.pageSize = 25; this.queryFilter = createQueryFilter(schema); - if (this.schema.hasOwnProperty('unique') === false) { + if (Object.prototype.hasOwnProperty.call(this.schema, 'unique') === false) { this.schema.unique = []; } } @@ -31,9 +50,9 @@ class CrudMapper { const withDeleted = params.deleted || params.disabled || false; const withCount = params._count || false; - let pageSize = parseInt(params._pageSize || params.pageSize || this.pageSize); + let pageSize = parseInt(params._pageSize || params.pageSize || this.pageSize, 10); const sortBy = params.sort || 'createdAt'; - const orderBy = parseInt(params.order || -1); + const orderBy = parseInt(params.order || -1, 10); const sort = {}; sort[sortBy] = orderBy; @@ -56,12 +75,12 @@ class CrudMapper { } }); - const page = parseInt(params.page || 1); + const page = parseInt(params.page || 1, 10); let skip = (page - 1) * pageSize; if (aggregateParam && page > 1) { const aux = pageSize; - pageSize = page * pageSize; + pageSize *= page; skip = pageSize - aux; } @@ -104,12 +123,12 @@ class CrudMapper { filter.deleted = { $ne: true }; } - return await this.collection.findOne(filter); + return this.collection.findOne(filter); } async create(post) { - post = this.validateAll(post); - const data = this.toDatabase(post); + const validated = this.validateAll(post); + const data = this.toDatabase(validated); await this.checkUniqueness(data); @@ -126,7 +145,7 @@ class CrudMapper { if (this.schema.unique.length > 0) { const orFilter = []; this.schema.unique.forEach((key) => { - if (data.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(data, key)) { if (typeof data[key] === 'object') { data[key].forEach((value) => { const obj = {}; @@ -154,7 +173,7 @@ class CrudMapper { const message = []; list.forEach((json) => { this.schema.unique.forEach((key) => { - if (data.hasOwnProperty(key) && json.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(data, key) && Object.prototype.hasOwnProperty.call(json, key)) { if (typeof data[key] === 'object') { data[key].forEach((dataValue) => { json[key].forEach((jsonValue) => { @@ -180,8 +199,8 @@ class CrudMapper { filter.deleted = { $ne: true }; } - post = this.validate(post); - const data = this.toDatabase(post); + const validated = this.validate(post); + const data = this.toDatabase(validated); await this.checkUniqueness(data, id); @@ -236,7 +255,7 @@ class CrudMapper { return result.ok === 1; } - toJson(data) { + static toJson(data) { const json = { id: data._id, ...data }; delete json._id; if (json.deleted === false) { @@ -248,7 +267,7 @@ class CrudMapper { } toHal(result, router) { - const json = this.toJson(result); + const json = CrudMapper.toJson(result); if (result.deleted === true) { if (result.deletedAt) { json.deletedAt = result.deletedAt; @@ -267,7 +286,7 @@ class CrudMapper { toHalCollection(result, ctx) { const entities = []; - for (let i = 0; i < result.result.length; i++) { + for (let i = 0; i < result.result.length; i += 1) { entities.push(this.toHal(result.result[i], ctx.router)); } @@ -283,10 +302,10 @@ class CrudMapper { _count: entities.length, }; - if (result.hasOwnProperty('count')) { + if (Object.prototype.hasOwnProperty.call(result, 'count')) { paginationData._total_items = result.count || 0; } - if (result.hasOwnProperty('page_count')) { + if (Object.prototype.hasOwnProperty.call(result, 'page_count')) { paginationData._page_count = result.page_count || 1; } @@ -304,7 +323,7 @@ class CrudMapper { query.page = result.page + 1; collection.link('next', `${ctx.router.url(this.listRoute)}?${queryString.stringify(query)}`); - if (result.hasOwnProperty('page_count') && result.page < result.page_count - 1) { + if (Object.prototype.hasOwnProperty.call(result, 'page_count') && result.page < result.page_count - 1) { query.page = result.page_count; collection.link('last', `${ctx.router.url(this.listRoute)}?${queryString.stringify(query)}`); } @@ -315,7 +334,7 @@ class CrudMapper { toDatabase(entity) { const data = entity; - if (data.hasOwnProperty('id')) { + if (Object.prototype.hasOwnProperty.call(data, 'id')) { data._id = data.id; delete data.id; } @@ -352,7 +371,7 @@ class CrudMapper { return Uuid.v4c(); } - getUUID() { + static getUUID() { return CrudMapper.generateUuid(); } @@ -362,16 +381,18 @@ class CrudMapper { * @param {[type]} key Data key name where Date type must be set on */ setDates(data, key) { - for (const x in data) { + Object.keys(data).forEach((x) => { if (typeof data[x] === 'object') { this.setDates(data[x], key); } else { const dateComparisonOperators = ['$gt', '$gte', '$lt', '$lte', '$ne', '$eq', '$in', '$nin']; - if ((key === x || dateComparisonOperators.indexOf(x) > -1) && moment(data[x], moment.ISO_8601, true).isValid()) { + if ((key === x || dateComparisonOperators.indexOf(x) > -1) + && moment(data[x], moment.ISO_8601, true).isValid()) { + // eslint-disable-next-line no-param-reassign data[x] = new Date(data[x]); } } - } + }); } /** @@ -381,32 +402,14 @@ class CrudMapper { * @return {[type]} [description] */ checkDates(schemaProperties, data) { - for (const k in schemaProperties) { + Object.keys(schemaProperties).forEach((k) => { if (typeof schemaProperties[k] === 'object' && schemaProperties[k].type && schemaProperties[k].type === 'array') { this.checkDates(schemaProperties[k].items.properties, data); } else if (schemaProperties[k].instanceOf && schemaProperties[k].instanceOf === 'Date') { this.setDates(data, k); } - } - } -} - -function createQueryFilter(schema) { - if (schema.hasOwnProperty('searchable') === false) { - schema.searchable = Object.keys(schema.properties); + }); } - const whitelist = { after: 1, before: 1, between: 1 }; - schema.searchable.forEach((key) => { - whitelist[key] = 1; - }); - return new MongoQF({ - custom: { - between: 'updatedAt', - after: 'updatedAt', - before: 'updatedAt', - }, - whitelist, - }); } module.exports = CrudMapper; diff --git a/src/crud/mongodb-query-filter.js b/src/crud/mongodb-query-filter.js index a58d07e..f13151a 100644 --- a/src/crud/mongodb-query-filter.js +++ b/src/crud/mongodb-query-filter.js @@ -1,4 +1,3 @@ - module.exports = function MongoQF(options) { const opts = options || {}; @@ -53,7 +52,8 @@ module.exports.prototype.customBBOX = (field) => (query, bbox) => { bboxArr[2] = parseFloat(bboxArr[2], 10); bboxArr[3] = parseFloat(bboxArr[3], 10); - if (!isNaN(bboxArr.reduce((a, b) => a + b))) { + if (!Number.isNaN(bboxArr.reduce((a, b) => a + b))) { + // eslint-disable-next-line no-param-reassign query[field] = { $geoWithin: { $geometry: { @@ -76,10 +76,11 @@ module.exports.prototype.customNear = (field) => (query, point) => { const pointArr = point.split(',').map((p) => parseFloat(p, 10)); if (pointArr.length >= 2) { - if (!isNaN(pointArr.reduce((a, b) => a + b))) { + if (!Number.isNaN(pointArr.reduce((a, b) => a + b))) { const max = pointArr[2]; const min = pointArr[3]; + // eslint-disable-next-line no-param-reassign query[field] = { $near: { $geometry: { @@ -89,10 +90,12 @@ module.exports.prototype.customNear = (field) => (query, point) => { }, }; - if (!isNaN(max)) { + if (!Number.isNaN(max)) { + // eslint-disable-next-line no-param-reassign query[field].$near.$maxDistance = max; - if (!isNaN(min)) { + if (!Number.isNaN(min)) { + // eslint-disable-next-line no-param-reassign query[field].$near.$minDistance = min; } } @@ -103,7 +106,7 @@ module.exports.prototype.customNear = (field) => (query, point) => { function parseDate(value) { let date = value; - if (!isNaN(date)) { + if (!Number.isNaN(date)) { if (`${date}`.length === 10) { date = `${date}000`; } @@ -116,6 +119,7 @@ function parseDate(value) { module.exports.prototype.customAfter = (field) => (query, value) => { const date = parseDate(value); if (date.toString() !== 'Invalid Date') { + // eslint-disable-next-line no-param-reassign query[field] = { $gte: date, }; @@ -126,6 +130,7 @@ module.exports.prototype.customBefore = (field) => (query, value) => { const date = parseDate(value); if (date.toString() !== 'Invalid Date') { + // eslint-disable-next-line no-param-reassign query[field] = { $lt: date, }; @@ -141,6 +146,7 @@ module.exports.prototype.customBetween = (field) => (query, value) => { const before = parseDate(beforeValue); if (after.toString() !== 'Invalid Date' && before.toString() !== 'Invalid Date') { + // eslint-disable-next-line no-param-reassign query[field] = { $gte: after, $lt: before, @@ -192,8 +198,10 @@ module.exports.prototype.parseString = function parseString(string, array) { } break; default: - ret.org = org = op + org; - ret.op = op = ''; + org = op + org; + op = ''; + ret.org = org; + ret.op = ''; ret.value = this.parseStringVal(org); if (array) { @@ -221,7 +229,7 @@ module.exports.prototype.parseStringVal = function parseStringVal(string) { return true; } if (this.string.toBoolean && string.toLowerCase() === 'false') { return false; - } if (this.string.toNumber && !isNaN(parseInt(string, 10)) + } if (this.string.toNumber && !Number.isNaN(parseInt(string, 10)) && ((+string - +string) + 1) >= 0) { return parseFloat(string, 10); } From 7619c66f3254cb19a7edbba61dabd2a8ac34613c Mon Sep 17 00:00:00 2001 From: Mario Costa Date: Mon, 7 Feb 2022 22:53:49 -0300 Subject: [PATCH 2/2] fix(mapper): prevent passing null in checkDates method --- src/crud/mapper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crud/mapper.js b/src/crud/mapper.js index b72d25e..610dde7 100644 --- a/src/crud/mapper.js +++ b/src/crud/mapper.js @@ -404,7 +404,7 @@ class CrudMapper { checkDates(schemaProperties, data) { Object.keys(schemaProperties).forEach((k) => { if (typeof schemaProperties[k] === 'object' && schemaProperties[k].type && schemaProperties[k].type === 'array') { - this.checkDates(schemaProperties[k].items.properties, data); + this.checkDates(schemaProperties[k].items.properties || {}, data); } else if (schemaProperties[k].instanceOf && schemaProperties[k].instanceOf === 'Date') { this.setDates(data, k); }