diff --git a/src/db.ts b/src/db.ts index 2b1f409..2bab005 100644 --- a/src/db.ts +++ b/src/db.ts @@ -7,7 +7,7 @@ import ChangelogModel from './models/changelog'; // database urls, prod db url is retrieved automatically const TESTING_URL = 'mongodb://localhost/dfopdb_test'; const DEV_URL = 'mongodb://localhost/dfopdb'; -const debugging = false; +const debugging = true; if (process.env.NODE_ENV !== 'production' && debugging) { mongoose.set('debug', true); // enable mongoose debug diff --git a/src/routes/sample.ts b/src/routes/sample.ts index f3395c8..ef87ab3 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -43,6 +43,54 @@ router.get('/samples', async (req, res, next) => { if (!filters['to-page']) { // set to-page default filters['to-page'] = 0; } + const addedFilter = filters.filters.find(e => e.field === 'added'); + if (addedFilter) { // convert added filter to object id + filters.filters.splice(filters.filters.findIndex(e => e.field === 'added'), 1); + if (addedFilter.mode === 'in') { + const v = []; // query value + addedFilter.values.forEach(value => { + const date = [new Date(value).setHours(0,0,0,0), new Date(value).setHours(23,59,59,999)]; + v.push({$and: [{ _id: { '$gte': dateToOId(date[0])}}, { _id: { '$lte': dateToOId(date[1])}}]}); + }); + filters.filters.push({mode: 'or', field: '_id', values: v}); + } + else if (addedFilter.mode === 'nin') { + addedFilter.values = addedFilter.values.sort(); + const v = []; // query value + + for (let i = 0; i <= addedFilter.values.length; i ++) { + v[i] = {$and: []}; + if (i > 0) { + const date = new Date(addedFilter.values[i - 1]).setHours(23,59,59,999); + v[i].$and.push({ _id: { '$gt': dateToOId(date)}}) ; + } + if (i < addedFilter.values.length) { + const date = new Date(addedFilter.values[i]).setHours(0,0,0,0); + v[i].$and.push({ _id: { '$lt': dateToOId(date)}}) ; + } + } + filters.filters.push({mode: 'or', field: '_id', values: v}); + } + else { + // start and end of day + const date = [new Date(addedFilter.values[0]).setHours(0,0,0,0), new Date(addedFilter.values[0]).setHours(23,59,59,999)]; + if (addedFilter.mode === 'lt') { // lt start + filters.filters.push({mode: 'lt', field: '_id', values: [dateToOId(date[0])]}); + } + if (addedFilter.mode === 'eq' || addedFilter.mode === 'lte') { // lte end + filters.filters.push({mode: 'lte', field: '_id', values: [dateToOId(date[1])]}); + } + if (addedFilter.mode === 'gt') { // gt end + filters.filters.push({mode: 'gt', field: '_id', values: [dateToOId(date[1])]}); + } + if (addedFilter.mode === 'eq' || addedFilter.mode === 'gte') { // gte start + filters.filters.push({mode: 'gte', field: '_id', values: [dateToOId(date[0])]}); + } + if (addedFilter.mode === 'ne') { + filters.filters.push({mode: 'or', field: '_id', values: [{ _id: { '$lt': dateToOId(date[0])}}, { _id: { '$gt': dateToOId(date[1])}}]}); + } + } + } const sortFilterKeys = filters.filters.map(e => e.field); @@ -80,7 +128,6 @@ router.get('/samples', async (req, res, next) => { {$addFields: {['sample.' + measurementName]: '$measurement.values'}}, // more restructuring {$replaceRoot: {newRoot: {$mergeObjects: [{$arrayElemAt: ['$sample', 0]}, {}]}}} ); - addFilterQueries(queryPtr, filters.filters.filter(e => sampleKeys.indexOf(e.field) >= 0)); // sample filters } else { // sorting with samples as starting collection collection = SampleModel; @@ -568,13 +615,23 @@ module.exports = router; async function numberGenerate (sample, req, res, next) { // generate number in format Location32, returns false on error const sampleData = await SampleModel - .findOne({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')}) - .sort({number: -1}) - .lean() + // .findOne({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')}) + // .sort({number: -1}) + // .lean() + .aggregate([ + {$match: {number: new RegExp('^' + 'Rng' + '[0-9]+$', 'm')}}, + // {$addFields: {number2: {$toDecimal: {$arrayElemAt: [{$split: [{$arrayElemAt: [{$split: ['$number', 'Rng']}, 1]}, '_']}, 0]}}}}, // not working with MongoDb 3.6 + {$addFields: {sortNumber: {$let: { + vars: {tmp: {$concat: ['000000000000000000000000000000', {$arrayElemAt: [{$split: [{$arrayElemAt: [{$split: ['$number', 'Rng']}, 1]}, '_']}, 0]}]}}, + in: {$substrCP: ['$$tmp', {$subtract: [{$strLenCP: '$$tmp'}, 30]}, {$strLenCP: '$$tmp'}]} + }}}}, + {$sort: {sortNumber: -1}}, + {$limit: 1} + ]) .exec() .catch(err => next(err)); if (sampleData instanceof Error) return false; - return req.authDetails.location + (sampleData ? Number(sampleData.number.replace(/[^0-9]+/g, '')) + 1 : 1); + return req.authDetails.location + (sampleData[0] ? Number(sampleData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1); } async function numberCheck(sample, res, next) { @@ -707,5 +764,16 @@ function addFilterQueries (queryPtr, filters) { // returns array of match queri } function filterQueries (filters) { - return filters.map(e => ({[e.field]: {['$' + e.mode]: (e.mode.indexOf('in') >= 0 ? e.values : e.values[0])}})) // add filter criteria as {field: {$mode: value}}, only use first value when mode is not in/nin + return filters.map(e => { + if (e.mode === 'or') { // allow or queries (needed for $ne added) + return {['$' + e.mode]: e.values}; + } + else { + return {[e.field]: {['$' + e.mode]: (e.mode.indexOf('in') >= 0 ? e.values : e.values[0])}}; // add filter criteria as {field: {$mode: value}}, only use first value when mode is not in/nin + } + }); +} + +function dateToOId (date) { // convert date to ObjectId + return mongoose.Types.ObjectId(Math.floor(date / 1000).toString(16) + '0000000000000000'); } \ No newline at end of file diff --git a/src/routes/validate/sample.ts b/src/routes/validate/sample.ts index f84a5be..ef0fa0a 100644 --- a/src/routes/validate/sample.ts +++ b/src/routes/validate/sample.ts @@ -175,7 +175,7 @@ export default class SampleValidate { let validator; let field = data.filters[i].field if (/material\./.test(field)) { // select right validation model - validator = MaterialValidate.outputV(); + validator = MaterialValidate.outputV().append({number: Joi.string().max(128).allow('')}); field = field.replace('material.', ''); } else if (/measurements\./.test(field)) { @@ -195,7 +195,7 @@ export default class SampleValidate { validator = Joi.object(this.sample); } const {value, error} = validator.validate({[field]: e}); - if (error) throw error; // reject invalid values + if (error) throw error; // reject invalid values // TODO: return exact error description, handle in frontend filters return value[field]; }); } @@ -215,7 +215,7 @@ export default class SampleValidate { filters: Joi.array().items(Joi.object({ mode: Joi.string().valid('eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'in', 'nin'), field: Joi.string().pattern(new RegExp('^(' + this.fieldKeys.join('|').replace(/\./g, '\\.').replace(/\*/g, '.+') + ')$', 'm')), - values: Joi.array().items(Joi.alternatives().try(Joi.string().max(128), Joi.number(), Joi.boolean())).min(1) + values: Joi.array().items(Joi.alternatives().try(Joi.string().max(128), Joi.number(), Joi.boolean(), Joi.date().iso())).min(1) })).default([]) }).with('to-page', 'page-size').validate(data); }