refactored user.ts
This commit is contained in:
		@@ -2,7 +2,7 @@
 | 
				
			|||||||
  get:
 | 
					  get:
 | 
				
			||||||
    summary: all samples in overview
 | 
					    summary: all samples in overview
 | 
				
			||||||
    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
					    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
				
			||||||
    x-doc: returns only samples with status 10  # TODO: methods /samples/new|deleted
 | 
					    x-doc: returns only samples with status 10
 | 
				
			||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - /sample
 | 
					      - /sample
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
@@ -18,6 +18,30 @@
 | 
				
			|||||||
        $ref: 'api.yaml#/components/responses/401'
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
      500:
 | 
					      500:
 | 
				
			||||||
        $ref: 'api.yaml#/components/responses/500'
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/samples{group}:
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - $ref: 'api.yaml#/components/parameters/Group'
 | 
				
			||||||
 | 
					  get:
 | 
				
			||||||
 | 
					    summary: all new/deleted samples in overview
 | 
				
			||||||
 | 
					    description: 'Auth: basic, levels: maintain, admin'
 | 
				
			||||||
 | 
					    x-doc: returns only samples with status 0/-1
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - /sample
 | 
				
			||||||
 | 
					    responses:
 | 
				
			||||||
 | 
					      200:
 | 
				
			||||||
 | 
					        description: samples overview
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              type: array
 | 
				
			||||||
 | 
					              items:
 | 
				
			||||||
 | 
					                $ref: 'api.yaml#/components/schemas/SampleRefs'
 | 
				
			||||||
 | 
					      401:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/401'
 | 
				
			||||||
 | 
					      500:
 | 
				
			||||||
 | 
					        $ref: 'api.yaml#/components/responses/500'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/sample/{id}:
 | 
					/sample/{id}:
 | 
				
			||||||
  parameters:
 | 
					  parameters:
 | 
				
			||||||
    - $ref: 'api.yaml#/components/parameters/Id'
 | 
					    - $ref: 'api.yaml#/components/parameters/Id'
 | 
				
			||||||
@@ -130,7 +154,7 @@
 | 
				
			|||||||
  get:
 | 
					  get:
 | 
				
			||||||
    summary: list all existing field names for custom notes fields
 | 
					    summary: list all existing field names for custom notes fields
 | 
				
			||||||
    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
					    description: 'Auth: all, levels: read, write, maintain, dev, admin'
 | 
				
			||||||
    x-doc: integrity has to be ensured  # TODO: implement mechanism to regularly check note_fields
 | 
					    x-doc: integrity has to be ensured
 | 
				
			||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - /sample
 | 
					      - /sample
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/api.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/api.ts
									
									
									
									
									
								
							@@ -20,7 +20,7 @@ export default class api {
 | 
				
			|||||||
      apiDoc = doc;
 | 
					      apiDoc = doc;
 | 
				
			||||||
      apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e));  // bundle routes
 | 
					      apiDoc.paths = apiDoc.paths.allOf.reduce((s, e) => Object.assign(s, e));  // bundle routes
 | 
				
			||||||
      apiDoc = this.resolveXDoc(apiDoc);
 | 
					      apiDoc = this.resolveXDoc(apiDoc);
 | 
				
			||||||
      oasParser.validate(apiDoc, (err, api) => {
 | 
					      oasParser.validate(apiDoc, (err, api) => {  // validate oas schema
 | 
				
			||||||
        if (err) {
 | 
					        if (err) {
 | 
				
			||||||
          console.error(err);
 | 
					          console.error(err);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -35,8 +35,8 @@ export default class api {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private static resolveXDoc (doc) {  // resolve x-doc properties recursively
 | 
					  private static resolveXDoc (doc) {  // resolve x-doc properties recursively
 | 
				
			||||||
    Object.keys(doc).forEach(key => {
 | 
					    Object.keys(doc).forEach(key => {
 | 
				
			||||||
      if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) {
 | 
					      if (doc[key] !== null && doc[key].hasOwnProperty('x-doc')) {  // add x-doc to description, is styled via css
 | 
				
			||||||
        doc[key].description += this.addHtml(doc[key]['x-doc']);
 | 
					        doc[key].description += '<details class="docs"><summary>docs</summary>' + doc[key]['x-doc'] + '</details>';
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else if (typeof doc[key] === 'object' && doc[key] !== null) {  // go deeper into recursion
 | 
					      else if (typeof doc[key] === 'object' && doc[key] !== null) {  // go deeper into recursion
 | 
				
			||||||
        doc[key] = this.resolveXDoc(doc[key]);
 | 
					        doc[key] = this.resolveXDoc(doc[key]);
 | 
				
			||||||
@@ -44,8 +44,4 @@ export default class api {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    return doc;
 | 
					    return doc;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static addHtml (text) {  // add docs HTML
 | 
					 | 
				
			||||||
    return '<details class="docs"><summary>docs</summary>' + text + '</details>';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/db.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/db.ts
									
									
									
									
									
								
							@@ -13,7 +13,7 @@ export default class db {
 | 
				
			|||||||
    mode: null,
 | 
					    mode: null,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static connect (mode = '', done: Function = () => {}) {  // set mode to test for unit/integration tests, otherwise skip parameter. done is also only needed for testing
 | 
					  static connect (mode = '', done: Function = () => {}) {  // set mode to test for unit/integration tests, otherwise skip parameters. done is also only needed for testing
 | 
				
			||||||
    if (this.state.db) return done();  // db is already connected
 | 
					    if (this.state.db) return done();  // db is already connected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // find right connection url
 | 
					    // find right connection url
 | 
				
			||||||
@@ -84,9 +84,9 @@ export default class db {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static loadJson (json, done: Function = () => {}) {  // insert given JSON data into db, uses core mongodb methods
 | 
					  static loadJson (json, done: Function = () => {}) {  // insert given JSON data into db, uses core mongodb methods
 | 
				
			||||||
    if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {
 | 
					    if (!this.state.db || !json.hasOwnProperty('collections') || json.collections.length === 0) {  // no db connection or nothing to load
 | 
				
			||||||
      return done();
 | 
					      return done();
 | 
				
			||||||
    }  // no db connection or nothing to load
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let loadCounter = 0;  // count number of loaded collections to know when to return done()
 | 
					    let loadCounter = 0;  // count number of loaded collections to know when to return done()
 | 
				
			||||||
    Object.keys(json.collections).forEach(collectionName => {  // create each collection
 | 
					    Object.keys(json.collections).forEach(collectionName => {  // create each collection
 | 
				
			||||||
@@ -103,10 +103,10 @@ export default class db {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private static oidResolve (object: any) {  // resolve $oid fields to actual ObjectIds recursively
 | 
					  private static oidResolve (object: any) {  // resolve $oid fields to actual ObjectIds recursively
 | 
				
			||||||
    Object.keys(object).forEach(key => {
 | 
					    Object.keys(object).forEach(key => {
 | 
				
			||||||
      if (object[key] !== null && object[key].hasOwnProperty('$oid')) {
 | 
					      if (object[key] !== null && object[key].hasOwnProperty('$oid')) {  // found oid, replace
 | 
				
			||||||
        object[key] = mongoose.Types.ObjectId(object[key].$oid);
 | 
					        object[key] = mongoose.Types.ObjectId(object[key].$oid);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else if (typeof object[key] === 'object' && object[key] !== null) {
 | 
					      else if (typeof object[key] === 'object' && object[key] !== null) {  // deeper into recursion
 | 
				
			||||||
        object[key] = this.oidResolve(object[key]);
 | 
					        object[key] = this.oidResolve(object[key]);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,7 @@ function basic (req, next): any {  // checks basic auth and returns changed user
 | 
				
			|||||||
        if (data.length === 1) {  // one user found
 | 
					        if (data.length === 1) {  // one user found
 | 
				
			||||||
          bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
 | 
					          bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
 | 
				
			||||||
            if (err) return next(err);
 | 
					            if (err) return next(err);
 | 
				
			||||||
            if (res === true) {
 | 
					            if (res === true) {  // password correct
 | 
				
			||||||
              resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
 | 
					              resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            else {
 | 
				
			||||||
@@ -84,7 +84,7 @@ function basic (req, next): any {  // checks basic auth and returns changed user
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function key (req, next): any {  // checks API key and returns changed user object
 | 
					function key (req, next): any {  // checks API key and returns changed user object
 | 
				
			||||||
  return new Promise(resolve => {
 | 
					  return new Promise(resolve => {
 | 
				
			||||||
    if (req.query.key !== undefined) {
 | 
					    if (req.query.key !== undefined) {  // key available
 | 
				
			||||||
      UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => {  // find user
 | 
					      UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => {  // find user
 | 
				
			||||||
        if (err) return next(err);
 | 
					        if (err) return next(err);
 | 
				
			||||||
        if (data.length === 1) {  // one user found
 | 
					        if (data.length === 1) {  // one user found
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// sends an email
 | 
					// sends an email using the BIC service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (mailAddress, subject, content, f) => {  // callback, executed empty or with error
 | 
					export default (mailAddress, subject, content, f) => {  // callback, executed empty or with error
 | 
				
			||||||
  if (process.env.NODE_ENV === 'production') {
 | 
					  if (process.env.NODE_ENV === 'production') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import mongoSanitize from 'mongo-sanitize';
 | 
				
			|||||||
import api from './api';
 | 
					import api from './api';
 | 
				
			||||||
import db from './db';
 | 
					import db from './db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: overall commenting/documentation review
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// tell if server is running in debug or production environment
 | 
					// tell if server is running in debug or production environment
 | 
				
			||||||
console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
 | 
					console.info(process.env.NODE_ENV === 'production' ? '===== PRODUCTION =====' : process.env.NODE_ENV === 'test' ? '' :'===== DEVELOPMENT =====');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,13 +37,14 @@ router.put('/condition/' + IdValidate.parameter(), async (req, res, next) => {
 | 
				
			|||||||
  if (!data) {
 | 
					  if (!data) {
 | 
				
			||||||
    res.status(404).json({status: 'Not found'});
 | 
					    res.status(404).json({status: 'Not found'});
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // add properties needed for sampleIdCheck
 | 
					  // add properties needed for sampleIdCheck
 | 
				
			||||||
  condition.treatment_template = data.treatment_template;
 | 
					  condition.treatment_template = data.treatment_template;
 | 
				
			||||||
  condition.sample_id = data.sample_id;
 | 
					  condition.sample_id = data.sample_id;
 | 
				
			||||||
  if (!await sampleIdCheck(condition, req, res, next)) return;
 | 
					  if (!await sampleIdCheck(condition, req, res, next)) return;
 | 
				
			||||||
  if (condition.parameters) {
 | 
					  if (condition.parameters) {
 | 
				
			||||||
    condition.parameters = _.assign({}, data.parameters, condition.parameters);
 | 
					    condition.parameters = _.assign({}, data.parameters, condition.parameters);
 | 
				
			||||||
    if (!_.isEqual(condition.parameters, data.parameters)) {
 | 
					    if (!_.isEqual(condition.parameters, data.parameters)) {  // parameters did not change
 | 
				
			||||||
      condition.status = 0;
 | 
					      condition.status = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -83,7 +84,7 @@ router.post('/condition/new', async (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  condition.number = await numberGenerate(condition, treatmentData, next);
 | 
					  condition.number = await numberGenerate(condition, treatmentData, next);
 | 
				
			||||||
  if (!condition.number) return;
 | 
					  if (!condition.number) return;
 | 
				
			||||||
  condition.status = 0;
 | 
					  condition.status = 0;  // set status to new
 | 
				
			||||||
  await new ConditionModel(condition).save((err, data) => {
 | 
					  await new ConditionModel(condition).save((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    res.json(ConditionValidate.output(data.toObject()));
 | 
					    res.json(ConditionValidate.output(data.toObject()));
 | 
				
			||||||
@@ -105,8 +106,8 @@ async function sampleIdCheck (condition, req, res, next) {  // validate sample_i
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function numberGenerate (condition, treatmentData, next) {  // validate number, returns false if invalid
 | 
					async function numberGenerate (condition, treatmentData, next) {  // generate number, returns false on error
 | 
				
			||||||
  const conditionData = await ConditionModel
 | 
					  const conditionData = await ConditionModel  // find condition with highest number belonging to the same sample
 | 
				
			||||||
    .find({sample_id: condition.sample_id, number: new RegExp('^' + treatmentData.number_prefix + '[0-9]+$', 'm')})
 | 
					    .find({sample_id: condition.sample_id, number: new RegExp('^' + treatmentData.number_prefix + '[0-9]+$', 'm')})
 | 
				
			||||||
    .sort({number: -1})
 | 
					    .sort({number: -1})
 | 
				
			||||||
    .limit(1)
 | 
					    .limit(1)
 | 
				
			||||||
@@ -114,7 +115,7 @@ async function numberGenerate (condition, treatmentData, next) {  // validate nu
 | 
				
			|||||||
    .exec()
 | 
					    .exec()
 | 
				
			||||||
    .catch(err => next(err)) as any;
 | 
					    .catch(err => next(err)) as any;
 | 
				
			||||||
  if (conditionData instanceof Error) return false;
 | 
					  if (conditionData instanceof Error) return false;
 | 
				
			||||||
  return treatmentData.number_prefix + (conditionData.length > 0 ? Number(conditionData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1);
 | 
					  return treatmentData.number_prefix + (conditionData.length > 0 ? Number(conditionData[0].number.replace(/[^0-9]+/g, '')) + 1 : 1);  // return new number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function treatmentCheck (condition, param, res, next) {  // validate treatment template, returns false if invalid, otherwise template data
 | 
					async function treatmentCheck (condition, param, res, next) {  // validate treatment template, returns false if invalid, otherwise template data
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,6 @@ router.get('/materials/:group(new|deleted)', (req, res, next) => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  MaterialModel.find({status: status}).lean().exec((err, data) => {
 | 
					  MaterialModel.find({status: status}).lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    console.log(data);
 | 
					 | 
				
			||||||
    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
					    res.json(_.compact(data.map(e => MaterialValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -68,7 +67,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // check for changes
 | 
					    // check for changes
 | 
				
			||||||
    if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) {
 | 
					    if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) {
 | 
				
			||||||
      material.status = 0;
 | 
					      material.status = 0;  // set status to new
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
 | 
					    await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
 | 
				
			||||||
@@ -102,13 +101,12 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
router.post('/material/new', async (req, res, next) => {
 | 
					router.post('/material/new', async (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // validate input
 | 
					 | 
				
			||||||
  const {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
					  const {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
				
			||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!await nameCheck(material, res, next)) return;
 | 
					  if (!await nameCheck(material, res, next)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  material.status = 0;
 | 
					  material.status = 0;  // set status to new
 | 
				
			||||||
  await new MaterialModel(material).save((err, data) => {
 | 
					  await new MaterialModel(material).save((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    res.json(MaterialValidate.output(data.toObject()));
 | 
					    res.json(MaterialValidate.output(data.toObject()));
 | 
				
			||||||
@@ -120,7 +118,7 @@ module.exports = router;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function nameCheck (material, res, next) {  // check if name was already taken
 | 
					async function nameCheck (material, res, next) {  // check if name was already taken
 | 
				
			||||||
  const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => {next(err); return false;}) as any;
 | 
					  const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
  if (materialData instanceof Error) return false;
 | 
					  if (materialData instanceof Error) return false;
 | 
				
			||||||
  if (materialData) {  // could not find material_id
 | 
					  if (materialData) {  // could not find material_id
 | 
				
			||||||
    res.status(400).json({status: 'Material name already taken'});
 | 
					    res.status(400).json({status: 'Material name already taken'});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,16 +36,20 @@ router.put('/measurement/' + IdValidate.parameter(), async (req, res, next) => {
 | 
				
			|||||||
  if (!data) {
 | 
					  if (!data) {
 | 
				
			||||||
    res.status(404).json({status: 'Not found'});
 | 
					    res.status(404).json({status: 'Not found'});
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // add properties needed for conditionIdCheck
 | 
					  // add properties needed for conditionIdCheck
 | 
				
			||||||
  measurement.measurement_template = data.measurement_template;
 | 
					  measurement.measurement_template = data.measurement_template;
 | 
				
			||||||
  measurement.condition_id = data.condition_id;
 | 
					  measurement.condition_id = data.condition_id;
 | 
				
			||||||
  if (!await conditionIdCheck(measurement, req, res, next)) return;
 | 
					  if (!await conditionIdCheck(measurement, req, res, next)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // check for changes
 | 
				
			||||||
  if (measurement.values) {
 | 
					  if (measurement.values) {
 | 
				
			||||||
    measurement.values = _.assign({}, data.values, measurement.values);
 | 
					    measurement.values = _.assign({}, data.values, measurement.values);
 | 
				
			||||||
    if (!_.isEqual(measurement.values, data.values)) {
 | 
					    if (!_.isEqual(measurement.values, data.values)) {
 | 
				
			||||||
      measurement.status = 0;
 | 
					      measurement.status = 0;  // set status to new
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
					  if (!await templateCheck(measurement, 'change', res, next)) return;
 | 
				
			||||||
  await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => {
 | 
					  await MeasurementModel.findByIdAndUpdate(req.params.id, measurement, {new: true}).lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
@@ -99,7 +103,7 @@ async function conditionIdCheck (measurement, req, res, next) {  // validate con
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function templateCheck (measurement, param, res, next) {  // validate measurement_template and values
 | 
					async function templateCheck (measurement, param, res, next) {  // validate measurement_template and values, param for new/change
 | 
				
			||||||
  const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template).lean().exec().catch(err => {next(err); return false;}) as any;
 | 
					  const templateData = await MeasurementTemplateModel.findById(measurement.measurement_template).lean().exec().catch(err => {next(err); return false;}) as any;
 | 
				
			||||||
  if (!templateData) {  // template not found
 | 
					  if (!templateData) {  // template not found
 | 
				
			||||||
    res.status(400).json({status: 'Measurement template not available'});
 | 
					    res.status(400).json({status: 'Measurement template not available'});
 | 
				
			||||||
@@ -108,7 +112,6 @@ async function templateCheck (measurement, param, res, next) {  // validate meas
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // validate values
 | 
					  // validate values
 | 
				
			||||||
  const {error, value: ignore} = ParametersValidate.input(measurement.values, templateData.parameters, param);
 | 
					  const {error, value: ignore} = ParametersValidate.input(measurement.values, templateData.parameters, param);
 | 
				
			||||||
  console.log(error);
 | 
					 | 
				
			||||||
  if (error) {res400(error, res); return false;}
 | 
					  if (error) {res400(error, res); return false;}
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -4,7 +4,6 @@ import NoteModel from '../models/note';
 | 
				
			|||||||
import NoteFieldModel from '../models/note_field';
 | 
					import NoteFieldModel from '../models/note_field';
 | 
				
			||||||
import TestHelper from "../test/helper";
 | 
					import TestHelper from "../test/helper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: think again which parameters are required at POST
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('/sample', () => {
 | 
					describe('/sample', () => {
 | 
				
			||||||
  let server;
 | 
					  let server;
 | 
				
			||||||
@@ -23,16 +22,16 @@ describe('/sample', () => {
 | 
				
			|||||||
        if (err) return done(err);
 | 
					        if (err) return done(err);
 | 
				
			||||||
        const json = require('../test/db.json');
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
        should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
 | 
					        should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 10).length);
 | 
				
			||||||
        should(res.body).matchEach(material => {
 | 
					        should(res.body).matchEach(sample => {
 | 
				
			||||||
          should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
					          should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
				
			||||||
          should(material).have.property('_id').be.type('string');
 | 
					          should(sample).have.property('_id').be.type('string');
 | 
				
			||||||
          should(material).have.property('number').be.type('string');
 | 
					          should(sample).have.property('number').be.type('string');
 | 
				
			||||||
          should(material).have.property('type').be.type('string');
 | 
					          should(sample).have.property('type').be.type('string');
 | 
				
			||||||
          should(material).have.property('color').be.type('string');
 | 
					          should(sample).have.property('color').be.type('string');
 | 
				
			||||||
          should(material).have.property('batch').be.type('string');
 | 
					          should(sample).have.property('batch').be.type('string');
 | 
				
			||||||
          should(material).have.property('material_id').be.type('string');
 | 
					          should(sample).have.property('material_id').be.type('string');
 | 
				
			||||||
          should(material).have.property('note_id');
 | 
					          should(sample).have.property('note_id');
 | 
				
			||||||
          should(material).have.property('user_id').be.type('string');
 | 
					          should(sample).have.property('user_id').be.type('string');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@@ -70,6 +69,94 @@ describe('/sample', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('GET /samples/{group}', () => {
 | 
				
			||||||
 | 
					    it('returns all new samples', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        let asyncCounter = res.body.length;
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === 0).length);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(sample => {
 | 
				
			||||||
 | 
					          should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
				
			||||||
 | 
					          should(sample).have.property('_id').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('number').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('type').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('color').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('batch').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('material_id').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('note_id');
 | 
				
			||||||
 | 
					          should(sample).have.property('user_id').be.type('string');
 | 
				
			||||||
 | 
					          SampleModel.findById(sample._id).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					            should(data).have.property('status', 0);
 | 
				
			||||||
 | 
					            if (--asyncCounter === 0) {
 | 
				
			||||||
 | 
					              done();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('returns all deleted samples', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples/deleted',
 | 
				
			||||||
 | 
					        auth: {basic: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        const json = require('../test/db.json');
 | 
				
			||||||
 | 
					        let asyncCounter = res.body.length;
 | 
				
			||||||
 | 
					        should(res.body).have.lengthOf(json.collections.samples.filter(e => e.status === -1).length);
 | 
				
			||||||
 | 
					        should(res.body).matchEach(sample => {
 | 
				
			||||||
 | 
					          should(sample).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
				
			||||||
 | 
					          should(sample).have.property('_id').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('number').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('type').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('color').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('batch').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('material_id').be.type('string');
 | 
				
			||||||
 | 
					          should(sample).have.property('note_id');
 | 
				
			||||||
 | 
					          should(sample).have.property('user_id').be.type('string');
 | 
				
			||||||
 | 
					          SampleModel.findById(sample._id).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					            should(data).have.property('status', -1);
 | 
				
			||||||
 | 
					            if (--asyncCounter === 0) {
 | 
				
			||||||
 | 
					              done();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects requests from a write user', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples/new',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 403
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects an API key', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples/new',
 | 
				
			||||||
 | 
					        auth: {key: 'admin'},
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('rejects unauthorized requests', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples/new',
 | 
				
			||||||
 | 
					        httpStatus: 401
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('PUT /sample/{id}', () => {
 | 
					  describe('PUT /sample/{id}', () => {
 | 
				
			||||||
    it('returns the right sample', done => {
 | 
					    it('returns the right sample', done => {
 | 
				
			||||||
      TestHelper.request(server, done, {
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
@@ -194,12 +281,10 @@ describe('/sample', () => {
 | 
				
			|||||||
      }).end(err => {
 | 
					      }).end(err => {
 | 
				
			||||||
        if (err) return done(err);
 | 
					        if (err) return done(err);
 | 
				
			||||||
        NoteFieldModel.findOne({name: 'not allowed for new applications'}).lean().exec((err, data) => {
 | 
					        NoteFieldModel.findOne({name: 'not allowed for new applications'}).lean().exec((err, data) => {
 | 
				
			||||||
          console.log(data);
 | 
					 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          should(data).have.property('qty', 1);
 | 
					          should(data).have.property('qty', 1);
 | 
				
			||||||
          NoteFieldModel.findOne({name: 'field1'}).lean().exec((err, data) => {
 | 
					          NoteFieldModel.findOne({name: 'field1'}).lean().exec((err, data) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            console.log(data);
 | 
					 | 
				
			||||||
            should(data).have.property('qty', 1);
 | 
					            should(data).have.property('qty', 1);
 | 
				
			||||||
            done();
 | 
					            done();
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
@@ -233,7 +318,6 @@ describe('/sample', () => {
 | 
				
			|||||||
        if (err) return done (err);
 | 
					        if (err) return done (err);
 | 
				
			||||||
        NoteModel.findById(res.body.note_id).lean().exec((err, data) => {
 | 
					        NoteModel.findById(res.body.note_id).lean().exec((err, data) => {
 | 
				
			||||||
          if (err) return done (err);
 | 
					          if (err) return done (err);
 | 
				
			||||||
          console.log(data);
 | 
					 | 
				
			||||||
          should(data).not.be.null();
 | 
					          should(data).not.be.null();
 | 
				
			||||||
          should(data).have.property('comment', 'Stoff gesperrt');
 | 
					          should(data).have.property('comment', 'Stoff gesperrt');
 | 
				
			||||||
          should(data).have.property('sample_references').have.lengthOf(0);
 | 
					          should(data).have.property('sample_references').have.lengthOf(0);
 | 
				
			||||||
@@ -448,7 +532,6 @@ describe('/sample', () => {
 | 
				
			|||||||
        setTimeout(() => {  // background action takes some time before we can check
 | 
					        setTimeout(() => {  // background action takes some time before we can check
 | 
				
			||||||
          NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
 | 
					          NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => {
 | 
				
			||||||
            if (err) return done(err);
 | 
					            if (err) return done(err);
 | 
				
			||||||
            console.log(data);
 | 
					 | 
				
			||||||
            should(data).have.property('sample_references').with.lengthOf(1);
 | 
					            should(data).have.property('sample_references').with.lengthOf(1);
 | 
				
			||||||
            should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
 | 
					            should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
 | 
				
			||||||
            should(data.sample_references[0]).have.property('relation', 'part to sample');
 | 
					            should(data.sample_references[0]).have.property('relation', 'part to sample');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,22 @@ router.get('/samples', (req, res, next) => {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get('/samples/:group(new|deleted)', (req, res, next) => {
 | 
				
			||||||
 | 
					  if (!req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let status;
 | 
				
			||||||
 | 
					  switch (req.params.group) {
 | 
				
			||||||
 | 
					    case 'new': status = 0;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'deleted': status = -1;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  SampleModel.find({status: status}).lean().exec((err, data) => {
 | 
				
			||||||
 | 
					    if (err) return next(err);
 | 
				
			||||||
 | 
					    res.json(_.compact(data.map(e => SampleValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
					router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,6 +49,7 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    if (!sampleData) {
 | 
					    if (!sampleData) {
 | 
				
			||||||
      return res.status(404).json({status: 'Not found'});
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // only maintain and admin are allowed to edit other user's data
 | 
					    // only maintain and admin are allowed to edit other user's data
 | 
				
			||||||
    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,12 +65,12 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
      if (sampleData.note_id !== null) {  // old notes data exists
 | 
					      if (sampleData.note_id !== null) {  // old notes data exists
 | 
				
			||||||
        const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any;
 | 
					        const data = await NoteModel.findById(sampleData.note_id).lean().exec().catch(err => {next(err);}) as any;
 | 
				
			||||||
        if (data instanceof Error) return;
 | 
					        if (data instanceof Error) return;
 | 
				
			||||||
        newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);
 | 
					        newNotes = !_.isEqual(_.pick(IdValidate.stringify(data), _.keys(sample.notes)), sample.notes);  // check if notes were changed
 | 
				
			||||||
        if (newNotes) {
 | 
					        if (newNotes) {
 | 
				
			||||||
          if (data.hasOwnProperty('custom_fields')) {  // update note_fields
 | 
					          if (data.hasOwnProperty('custom_fields')) {  // update note_fields
 | 
				
			||||||
            customFieldsChange(Object.keys(data.custom_fields), -1);
 | 
					            customFieldsChange(Object.keys(data.custom_fields), -1);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => {  // delete old notes
 | 
					          await NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec(err => {  // delete old notes
 | 
				
			||||||
            if (err) return console.error(err);
 | 
					            if (err) return console.error(err);
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -74,7 +91,8 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
 | 
					    if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) {
 | 
				
			||||||
      sample.status = 0;
 | 
					      sample.status = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
 | 
					
 | 
				
			||||||
 | 
					    await SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      res.json(SampleValidate.output(data));
 | 
					      res.json(SampleValidate.output(data));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -90,12 +108,13 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => {
 | 
				
			|||||||
    if (!sampleData) {
 | 
					    if (!sampleData) {
 | 
				
			||||||
      return res.status(404).json({status: 'Not found'});
 | 
					      return res.status(404).json({status: 'Not found'});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // only maintain and admin are allowed to edit other user's data
 | 
					    // only maintain and admin are allowed to edit other user's data
 | 
				
			||||||
    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
					    if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {  // set sample status
 | 
					    await SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => {  // set sample status
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      if (sampleData.note_id !== null) {
 | 
					      if (sampleData.note_id !== null) {  // handle notes
 | 
				
			||||||
        NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => {  // find notes to update note_fields
 | 
					        NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => {  // find notes to update note_fields
 | 
				
			||||||
          if (err) return next(err);
 | 
					          if (err) return next(err);
 | 
				
			||||||
          if (data.hasOwnProperty('custom_fields')) {  // update note_fields
 | 
					          if (data.hasOwnProperty('custom_fields')) {  // update note_fields
 | 
				
			||||||
@@ -124,15 +143,15 @@ router.post('/sample/new', async (req, res, next) => {
 | 
				
			|||||||
    customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
 | 
					    customFieldsChange(Object.keys(sample.notes.custom_fields), 1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sample.status = 0;
 | 
					  sample.status = 0;  // set status to new
 | 
				
			||||||
  sample.number = await numberGenerate(sample, req, res, next);
 | 
					  sample.number = await numberGenerate(sample, req, res, next);
 | 
				
			||||||
  if (!sample.number) return;
 | 
					  if (!sample.number) return;
 | 
				
			||||||
  new NoteModel(sample.notes).save((err, data) => {
 | 
					
 | 
				
			||||||
 | 
					  await new NoteModel(sample.notes).save((err, data) => {  // save notes
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    delete sample.notes;
 | 
					    delete sample.notes;
 | 
				
			||||||
    sample.note_id = data._id;
 | 
					    sample.note_id = data._id;
 | 
				
			||||||
    sample.user_id = req.authDetails.id;
 | 
					    sample.user_id = req.authDetails.id;
 | 
				
			||||||
    console.log(sample);
 | 
					 | 
				
			||||||
    new SampleModel(sample).save((err, data) => {
 | 
					    new SampleModel(sample).save((err, data) => {
 | 
				
			||||||
      if (err) return next(err);
 | 
					      if (err) return next(err);
 | 
				
			||||||
      res.json(SampleValidate.output(data.toObject()));
 | 
					      res.json(SampleValidate.output(data.toObject()));
 | 
				
			||||||
@@ -153,7 +172,7 @@ router.get('/sample/notes/fields', (req, res, next) => {
 | 
				
			|||||||
module.exports = router;
 | 
					module.exports = router;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function numberGenerate (sample, req, res, next) {  // validate number, returns false if invalid
 | 
					async function numberGenerate (sample, req, res, next) {  // generate number, returns false on error
 | 
				
			||||||
  const sampleData = await SampleModel
 | 
					  const sampleData = await SampleModel
 | 
				
			||||||
    .find({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
 | 
					    .find({number: new RegExp('^' + req.authDetails.location + '[0-9]+$', 'm')})
 | 
				
			||||||
    .lean()
 | 
					    .lean()
 | 
				
			||||||
@@ -180,7 +199,8 @@ async function materialCheck (sample, res, next, id = sample.material_id) {  //
 | 
				
			|||||||
function sampleRefCheck (sample, res, next) {  // validate sample_references, resolves false for invalid reference
 | 
					function sampleRefCheck (sample, res, next) {  // validate sample_references, resolves false for invalid reference
 | 
				
			||||||
  return new Promise(resolve => {
 | 
					  return new Promise(resolve => {
 | 
				
			||||||
    if (sample.notes.sample_references.length > 0) {  // there are sample_references
 | 
					    if (sample.notes.sample_references.length > 0) {  // there are sample_references
 | 
				
			||||||
      let referencesCount = sample.notes.sample_references.length;
 | 
					      let referencesCount = sample.notes.sample_references.length;  // count to keep track of running async operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      sample.notes.sample_references.forEach(reference => {
 | 
					      sample.notes.sample_references.forEach(reference => {
 | 
				
			||||||
        SampleModel.findById(reference.id).lean().exec((err, data) => {
 | 
					        SampleModel.findById(reference.id).lean().exec((err, data) => {
 | 
				
			||||||
          if (err) {next(err); resolve(false)}
 | 
					          if (err) {next(err); resolve(false)}
 | 
				
			||||||
@@ -189,7 +209,7 @@ function sampleRefCheck (sample, res, next) {  // validate sample_references, re
 | 
				
			|||||||
            return resolve(false);
 | 
					            return resolve(false);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          referencesCount --;
 | 
					          referencesCount --;
 | 
				
			||||||
          if (referencesCount <= 0) {
 | 
					          if (referencesCount <= 0) {  // all async requests done
 | 
				
			||||||
            resolve(true);
 | 
					            resolve(true);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -201,7 +221,7 @@ function sampleRefCheck (sample, res, next) {  // validate sample_references, re
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function customFieldsChange (fields, amount) {
 | 
					function customFieldsChange (fields, amount) {  // update custom_fields and respective quantities
 | 
				
			||||||
  fields.forEach(field => {
 | 
					  fields.forEach(field => {
 | 
				
			||||||
    NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}}, {new: true}).lean().exec((err, data: any) => {  // check if field exists
 | 
					    NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: amount}}, {new: true}).lean().exec((err, data: any) => {  // check if field exists
 | 
				
			||||||
      if (err) return console.error(err);
 | 
					      if (err) return console.error(err);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -200,7 +200,6 @@ describe('/template', () => {
 | 
				
			|||||||
          httpStatus: 200,
 | 
					          httpStatus: 200,
 | 
				
			||||||
          req: {parameters: [{name: 'time', range: {type: 'array'}}]}
 | 
					          req: {parameters: [{name: 'time', range: {type: 'array'}}]}
 | 
				
			||||||
        }).end((err, res) => {
 | 
					        }).end((err, res) => {
 | 
				
			||||||
          console.log(res.body);
 | 
					 | 
				
			||||||
          if (err) return done(err);
 | 
					          if (err) return done(err);
 | 
				
			||||||
          should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]});
 | 
					          should(_.omit(res.body, '_id')).be.eql({name: 'heat treatment', version: 2, number_prefix: 'A', parameters: [{name: 'time', range: {type: 'array'}}]});
 | 
				
			||||||
          done();
 | 
					          done();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ const router = express.Router();
 | 
				
			|||||||
router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
 | 
					router.get('/template/:collection(measurements|treatments)', (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  req.params.collection = req.params.collection.replace(/s$/g, '');
 | 
					  req.params.collection = req.params.collection.replace(/s$/g, '');  // remove trailing s
 | 
				
			||||||
  model(req).find({}).lean().exec((err, data) => {
 | 
					  model(req).find({}).lean().exec((err, data) => {
 | 
				
			||||||
     if (err) next (err);
 | 
					     if (err) next (err);
 | 
				
			||||||
     res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection))));  // validate all and filter null values from validation errors
 | 
					     res.json(_.compact(data.map(e => TemplateValidate.output(e, req.params.collection))));  // validate all and filter null values from validation errors
 | 
				
			||||||
@@ -52,8 +52,8 @@ router.put('/template/:collection(measurement|treatment)/' + IdValidate.paramete
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) {  // data was changed
 | 
					  if (!_.isEqual(_.pick(templateData, _.keys(template)), template)) {  // data was changed
 | 
				
			||||||
    template.version = templateData.version + 1;
 | 
					    template.version = templateData.version + 1;  // increase version
 | 
				
			||||||
    await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {
 | 
					    await new (model(req))(_.assign({}, _.omit(templateData, ['_id', '__v']), template)).save((err, data) => {  // save new template, fill with old properties
 | 
				
			||||||
      if (err) next (err);
 | 
					      if (err) next (err);
 | 
				
			||||||
      res.json(TemplateValidate.output(data.toObject(), req.params.collection));
 | 
					      res.json(TemplateValidate.output(data.toObject(), req.params.collection));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -73,7 +73,7 @@ router.post('/template/:collection(measurement|treatment)/new', async (req, res,
 | 
				
			|||||||
    if (!await numberPrefixCheck(template, req, res, next)) return;
 | 
					    if (!await numberPrefixCheck(template, req, res, next)) return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template.version = 1;
 | 
					  template.version = 1;  // set template version
 | 
				
			||||||
  await new (model(req))(template).save((err, data) => {
 | 
					  await new (model(req))(template).save((err, data) => {
 | 
				
			||||||
    if (err) next (err);
 | 
					    if (err) next (err);
 | 
				
			||||||
    res.json(TemplateValidate.output(data.toObject(), req.params.collection));
 | 
					    res.json(TemplateValidate.output(data.toObject(), req.params.collection));
 | 
				
			||||||
@@ -84,7 +84,7 @@ router.post('/template/:collection(measurement|treatment)/new', async (req, res,
 | 
				
			|||||||
module.exports = router;
 | 
					module.exports = router;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function numberPrefixCheck (template, req, res, next) {
 | 
					async function numberPrefixCheck (template, req, res, next) {  // check if number_prefix is available
 | 
				
			||||||
  const data = await model(req).findOne({number_prefix: template.number_prefix}).lean().exec().catch(err => {next(err); return false;}) as any;
 | 
					  const data = await model(req).findOne({number_prefix: template.number_prefix}).lean().exec().catch(err => {next(err); return false;}) as any;
 | 
				
			||||||
  if (data) {
 | 
					  if (data) {
 | 
				
			||||||
    res.status(400).json({status: 'Number prefix already taken'});
 | 
					    res.status(400).json({status: 'Number prefix already taken'});
 | 
				
			||||||
@@ -93,6 +93,6 @@ async function numberPrefixCheck (template, req, res, next) {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function model (req) {
 | 
					function model (req) {  // return right template model
 | 
				
			||||||
  return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
 | 
					  return req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -20,14 +20,10 @@ router.get('/users', (req, res) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
					router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
				
			||||||
  req.params.username = req.params[0];
 | 
					 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
  let username = req.authDetails.username;
 | 
					 | 
				
			||||||
  if (req.params.username !== undefined) {
 | 
					 | 
				
			||||||
    if (!req.auth(res, ['admin'], 'basic')) return;
 | 
					 | 
				
			||||||
    username = req.params.username;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const username = getUsername(req, res);
 | 
				
			||||||
 | 
					  if (!username) return;
 | 
				
			||||||
  UserModel.findOne({name: username}).lean().exec(  (err, data:any) => {
 | 
					  UserModel.findOne({name: username}).lean().exec(  (err, data:any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
@@ -39,14 +35,13 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
 | 
					router.put('/user:username([/](?!key|new).?*|/?)', async (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new
 | 
				
			||||||
  req.params.username = req.params[0];
 | 
					 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
  let username = req.authDetails.username;
 | 
					
 | 
				
			||||||
  if (req.params.username !== undefined) {
 | 
					  const username = getUsername(req, res);
 | 
				
			||||||
    if (!req.auth(res, ['admin'], 'basic')) return;
 | 
					  if (!username) return;
 | 
				
			||||||
    username = req.params.username;
 | 
					  console.log(username);
 | 
				
			||||||
  }
 | 
					
 | 
				
			||||||
  const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
 | 
					  const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
 | 
				
			||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,14 +51,10 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // check that user does not already exist if new name was specified
 | 
					  // check that user does not already exist if new name was specified
 | 
				
			||||||
  if (user.hasOwnProperty('name') && user.name !== username) {
 | 
					  if (user.hasOwnProperty('name') && user.name !== username) {
 | 
				
			||||||
    UserModel.find({name: user.name}).lean().exec(  (err, data:any) => {
 | 
					    if (!await usernameCheck(user.name, res, next)) return;
 | 
				
			||||||
      if (err) return next(err);
 | 
					 | 
				
			||||||
      if (data.length > 0 || UserValidate.isSpecialName(user.name)) {
 | 
					 | 
				
			||||||
        res.status(400).json({status: 'Username already taken'});
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec(  (err, data:any) => {
 | 
					  await UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec(  (err, data:any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    if (data) {
 | 
					    if (data) {
 | 
				
			||||||
      res.json(UserValidate.output(data));
 | 
					      res.json(UserValidate.output(data));
 | 
				
			||||||
@@ -73,28 +64,12 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    UserModel.findOneAndUpdate({name: username}, user, {new: true}).lean().exec(  (err, data:any) => {
 | 
					 | 
				
			||||||
      if (err) return next(err);
 | 
					 | 
				
			||||||
      if (data) {
 | 
					 | 
				
			||||||
        res.json(UserValidate.output(data));  // validate all and filter null values from validation errors
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        res.status(404).json({status: 'Not found'});
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
					router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // this path matches /user, /user/ and /user/xxx, but not /user/key or user/new. See https://forbeslindesay.github.io/express-route-tester/ for the generated regex
 | 
				
			||||||
  req.params.username = req.params[0];
 | 
					 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
				
			||||||
  let username = req.authDetails.username;
 | 
					
 | 
				
			||||||
  if (req.params.username !== undefined) {
 | 
					  const username = getUsername(req, res);
 | 
				
			||||||
    if (!req.auth(res, ['admin'], 'basic')) return;
 | 
					  if (!username) return;
 | 
				
			||||||
    username = req.params.username;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  UserModel.findOneAndDelete({name: username}).lean().exec(  (err, data:any) => {
 | 
					  UserModel.findOneAndDelete({name: username}).lean().exec(  (err, data:any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
@@ -116,7 +91,7 @@ router.get('/user/key', (req, res, next) => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.post('/user/new', (req, res, next) => {
 | 
					router.post('/user/new', async (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
					  if (!req.auth(res, ['admin'], 'basic')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // validate input
 | 
					  // validate input
 | 
				
			||||||
@@ -124,12 +99,7 @@ router.post('/user/new', (req, res, next) => {
 | 
				
			|||||||
  if (error) return res400(error, res);
 | 
					  if (error) return res400(error, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // check that user does not already exist
 | 
					  // check that user does not already exist
 | 
				
			||||||
  UserModel.find({name: user.name}).lean().exec(  (err, data:any) => {
 | 
					  if (!await usernameCheck(user.name, res, next)) return;
 | 
				
			||||||
    if (err) return next(err);
 | 
					 | 
				
			||||||
    if (data.length > 0  || UserValidate.isSpecialName(user.name)) {
 | 
					 | 
				
			||||||
      res.status(400).json({status: 'Username already taken'});
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  user.key = mongoose.Types.ObjectId();  // use object id as unique API key
 | 
					  user.key = mongoose.Types.ObjectId();  // use object id as unique API key
 | 
				
			||||||
  bcrypt.hash(user.pass, 10, (err, hash) => {  // password hashing
 | 
					  bcrypt.hash(user.pass, 10, (err, hash) => {  // password hashing
 | 
				
			||||||
@@ -140,18 +110,20 @@ router.post('/user/new', (req, res, next) => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.post('/user/passreset', (req, res, next) => {
 | 
					router.post('/user/passreset', (req, res, next) => {
 | 
				
			||||||
  // check if user/email combo exists
 | 
					  // check if user/email combo exists
 | 
				
			||||||
  UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
 | 
					  UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    if (data.length === 1) {  // it exists
 | 
					    if (data.length === 1) {  // it exists
 | 
				
			||||||
      const newPass = Math.random().toString(36).substring(2);
 | 
					      const newPass = Math.random().toString(36).substring(2);  // generate temporary password
 | 
				
			||||||
      bcrypt.hash(newPass, 10, (err, hash) => {  // password hashing
 | 
					      bcrypt.hash(newPass, 10, (err, hash) => {  // password hashing
 | 
				
			||||||
        if (err) return next(err);
 | 
					        if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}, err => {  // write new password
 | 
					        UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}, err => {  // write new password
 | 
				
			||||||
          if (err) return next(err);
 | 
					          if (err) return next(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // send email
 | 
				
			||||||
          mail(data[0].email, 'Your new password for the DFOP database', 'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.<br><br>The DFOP team', err => {
 | 
					          mail(data[0].email, 'Your new password for the DFOP database', 'Hi, <br><br> You requested to reset your password.<br>Your new password is:<br><br>' + newPass + '<br><br>If you did not request a password reset, talk to the sysadmin quickly!<br><br>Have a nice day.<br><br>The DFOP team', err => {
 | 
				
			||||||
            if (err) return next(err);
 | 
					            if (err) return next(err);
 | 
				
			||||||
            res.json({status: 'OK'});
 | 
					            res.json({status: 'OK'});
 | 
				
			||||||
@@ -167,3 +139,26 @@ router.post('/user/passreset', (req, res, next) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = router;
 | 
					module.exports = router;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getUsername (req, res) {  // returns username or false if action is not allowed
 | 
				
			||||||
 | 
					  req.params.username = req.params[0];  // because of path regex
 | 
				
			||||||
 | 
					  if (req.params.username !== undefined) {  // different username than request user
 | 
				
			||||||
 | 
					    if (!req.auth(res, ['admin'], 'basic')) return false;
 | 
				
			||||||
 | 
					    return req.params.username;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {
 | 
				
			||||||
 | 
					    return req.authDetails.username;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function usernameCheck (name, res, next) {  // check if username is already taken
 | 
				
			||||||
 | 
					  const userData = await UserModel.findOne({name: name}).lean().exec().catch(err => next(err)) as any;
 | 
				
			||||||
 | 
					  if (userData instanceof Error) return false;
 | 
				
			||||||
 | 
					  console.log(userData);
 | 
				
			||||||
 | 
					  console.log(UserValidate.isSpecialName(name));
 | 
				
			||||||
 | 
					  if (userData || UserValidate.isSpecialName(name)) {
 | 
				
			||||||
 | 
					    res.status(400).json({status: 'Username already taken'});
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,7 +18,7 @@ export default class ConditionValidate {
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param) {
 | 
					  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      return Joi.object({
 | 
					      return Joi.object({
 | 
				
			||||||
        sample_id: IdValidate.get().required(),
 | 
					        sample_id: IdValidate.get().required(),
 | 
				
			||||||
@@ -36,7 +36,7 @@ export default class ConditionValidate {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@ import Joi from '@hapi/joi';
 | 
				
			|||||||
export default class IdValidate {
 | 
					export default class IdValidate {
 | 
				
			||||||
  private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
 | 
					  private static id = Joi.string().pattern(new RegExp('[0-9a-f]{24}')).length(24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static get () {
 | 
					  static get () {  // return joi validation
 | 
				
			||||||
    return this.id;
 | 
					    return this.id;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static valid (id) {
 | 
					  static valid (id) {  // validate id
 | 
				
			||||||
    return this.id.validate(id).error === undefined;
 | 
					    return this.id.validate(id).error === undefined;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,11 +15,14 @@ export default class IdValidate {
 | 
				
			|||||||
    return ':id([0-9a-f]{24})';
 | 
					    return ':id([0-9a-f]{24})';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static stringify (data) {
 | 
					  static stringify (data) {  // convert all ObjectID objects to plain strings
 | 
				
			||||||
    Object.keys(data).forEach(key => {
 | 
					    Object.keys(data).forEach(key => {
 | 
				
			||||||
      if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
 | 
					      if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {  // stringify id
 | 
				
			||||||
        data[key] = data[key].toString();
 | 
					        data[key] = data[key].toString();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      else if (typeof data[key] === 'object' && data[key] !== null) {  // deeper into recursion
 | 
				
			||||||
 | 
					        data[key] = this.stringify(data[key]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    return data;
 | 
					    return data;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,7 @@ export default class MaterialValidate {  // validate input for material
 | 
				
			|||||||
      }))
 | 
					      }))
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param) {  // validate data, param: new(everything required)/change(available attributes are validated)
 | 
					  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      return joi.object({
 | 
					      return joi.object({
 | 
				
			||||||
        name: this.material.name.required(),
 | 
					        name: this.material.name.required(),
 | 
				
			||||||
@@ -68,7 +68,7 @@ export default class MaterialValidate {  // validate input for material
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    const {value, error} = joi.object({
 | 
					    const {value, error} = joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ export default class MeasurementValidate {
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param) {
 | 
					  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      return Joi.object({
 | 
					      return Joi.object({
 | 
				
			||||||
        condition_id: IdValidate.get().required(),
 | 
					        condition_id: IdValidate.get().required(),
 | 
				
			||||||
@@ -33,7 +33,7 @@ export default class MeasurementValidate {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ export default class NoteFieldValidate {
 | 
				
			|||||||
    qty: Joi.number()
 | 
					    qty: Joi.number()
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      name: this.note_field.name,
 | 
					      name: this.note_field.name,
 | 
				
			||||||
      qty: this.note_field.qty
 | 
					      qty: this.note_field.qty
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ export default class ParametersValidate {
 | 
				
			|||||||
  static input (data, parameters, param) {  // data to validate, parameters from template, param: 'new', 'change'
 | 
					  static input (data, parameters, param) {  // data to validate, parameters from template, param: 'new', 'change'
 | 
				
			||||||
    let joiObject = {};
 | 
					    let joiObject = {};
 | 
				
			||||||
    parameters.forEach(parameter => {
 | 
					    parameters.forEach(parameter => {
 | 
				
			||||||
      if (parameter.range.hasOwnProperty('values')) {
 | 
					      if (parameter.range.hasOwnProperty('values')) {  // append right validation method according to parameter
 | 
				
			||||||
        joiObject[parameter.name] = Joi.alternatives()
 | 
					        joiObject[parameter.name] = Joi.alternatives()
 | 
				
			||||||
          .try(Joi.string().max(128), Joi.number(), Joi.boolean())
 | 
					          .try(Joi.string().max(128), Joi.number(), Joi.boolean())
 | 
				
			||||||
          .valid(...parameter.range.values);
 | 
					          .valid(...parameter.range.values);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					// respond with 400 and include error details from the joi validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function res400 (error, res) {
 | 
					export default function res400 (error, res) {
 | 
				
			||||||
  res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
 | 
					  res.status(400).json({status: 'Invalid body format', details: error.details[0].message});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -41,7 +41,7 @@ export default class SampleValidate {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param) {  // validate data, param: new(everything required)/change(available attributes are validated)
 | 
					  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      return Joi.object({
 | 
					      return Joi.object({
 | 
				
			||||||
        color: this.sample.color.required(),
 | 
					        color: this.sample.color.required(),
 | 
				
			||||||
@@ -65,7 +65,7 @@ export default class SampleValidate {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ export default class TemplateValidate {
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param, template) {  // validate data, param: new(everything required)/change(available attributes are validated)
 | 
					  static input (data, param, template) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      if (template === 'treatment') {
 | 
					      if (template === 'treatment') {
 | 
				
			||||||
        return Joi.object({
 | 
					        return Joi.object({
 | 
				
			||||||
@@ -79,10 +79,10 @@ export default class TemplateValidate {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data, template) {  // validate output from database for needed properties, strip everything else
 | 
					  static output (data, template) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    let joiObject;
 | 
					    let joiObject;
 | 
				
			||||||
    if (template === 'treatment') {
 | 
					    if (template === 'treatment') {  // differentiate between measurement and treatment (has number_prefix) template
 | 
				
			||||||
      joiObject = {
 | 
					      joiObject = {
 | 
				
			||||||
        _id: IdValidate.get(),
 | 
					        _id: IdValidate.get(),
 | 
				
			||||||
        name: this.template.name,
 | 
					        name: this.template.name,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ export default class UserValidate {  // validate input for user
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset'];  // names a user cannot take
 | 
					  private static specialUsernames = ['admin', 'user', 'key', 'new', 'passreset'];  // names a user cannot take
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static input (data, param) {
 | 
					  static input (data, param) {  // validate input, set param to 'new' to make all attributes required
 | 
				
			||||||
    if (param === 'new') {
 | 
					    if (param === 'new') {
 | 
				
			||||||
      return Joi.object({
 | 
					      return Joi.object({
 | 
				
			||||||
        name: this.user.name.required(),
 | 
					        name: this.user.name.required(),
 | 
				
			||||||
@@ -68,7 +68,7 @@ export default class UserValidate {  // validate input for user
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
					  static output (data) {  // validate output and strip unwanted properties, returns null if not valid
 | 
				
			||||||
    data = IdValidate.stringify(data);
 | 
					    data = IdValidate.stringify(data);
 | 
				
			||||||
    const {value, error} = Joi.object({
 | 
					    const {value, error} = Joi.object({
 | 
				
			||||||
      _id: IdValidate.get(),
 | 
					      _id: IdValidate.get(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,13 +4,13 @@ import db from "../db";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class TestHelper {
 | 
					export default class TestHelper {
 | 
				
			||||||
  public static auth = {
 | 
					  public static auth = {  // test user credentials
 | 
				
			||||||
    admin: {pass: 'Abc123!#', key: '000000000000000000001003'},
 | 
					    admin: {pass: 'Abc123!#', key: '000000000000000000001003'},
 | 
				
			||||||
    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'},
 | 
					    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002'},
 | 
				
			||||||
    user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
 | 
					    user: {pass: 'Xyz890*)', key: '000000000000000000001001'},
 | 
				
			||||||
    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
 | 
					    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004'}
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  public static res = {
 | 
					  public static res = {  // default responses
 | 
				
			||||||
    400: {status: 'Bad request'},
 | 
					    400: {status: 'Bad request'},
 | 
				
			||||||
    401: {status: 'Unauthorized'},
 | 
					    401: {status: 'Unauthorized'},
 | 
				
			||||||
    403: {status: 'Forbidden'},
 | 
					    403: {status: 'Forbidden'},
 | 
				
			||||||
@@ -40,10 +40,10 @@ export default class TestHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static request (server, done, options) {  // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
 | 
					  static request (server, done, options) {  // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res}
 | 
				
			||||||
    let st = supertest(server);
 | 
					    let st = supertest(server);
 | 
				
			||||||
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {
 | 
					    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {  // resolve API key
 | 
				
			||||||
      options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
 | 
					      options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    switch (options.method) {
 | 
					    switch (options.method) {  // http method
 | 
				
			||||||
      case 'get':
 | 
					      case 'get':
 | 
				
			||||||
        st = st.get(options.url)
 | 
					        st = st.get(options.url)
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@@ -57,10 +57,10 @@ export default class TestHelper {
 | 
				
			|||||||
        st = st.delete(options.url)
 | 
					        st = st.delete(options.url)
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (options.hasOwnProperty('req')) {
 | 
					    if (options.hasOwnProperty('req')) {  // request body
 | 
				
			||||||
      st = st.send(options.req);
 | 
					      st = st.send(options.req);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {
 | 
					    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {  // resolve basic auth
 | 
				
			||||||
      if (this.auth.hasOwnProperty(options.auth.basic)) {
 | 
					      if (this.auth.hasOwnProperty(options.auth.basic)) {
 | 
				
			||||||
        st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
 | 
					        st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -70,21 +70,21 @@ export default class TestHelper {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    st = st.expect('Content-type', /json/)
 | 
					    st = st.expect('Content-type', /json/)
 | 
				
			||||||
      .expect(options.httpStatus);
 | 
					      .expect(options.httpStatus);
 | 
				
			||||||
    if (options.hasOwnProperty('res')) {
 | 
					    if (options.hasOwnProperty('res')) {  // evaluate result
 | 
				
			||||||
      return st.end((err, res) => {
 | 
					      return st.end((err, res) => {
 | 
				
			||||||
        if (err) return done (err);
 | 
					        if (err) return done (err);
 | 
				
			||||||
        should(res.body).be.eql(options.res);
 | 
					        should(res.body).be.eql(options.res);
 | 
				
			||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {
 | 
					    else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {  // evaluate default results
 | 
				
			||||||
      return st.end((err, res) => {
 | 
					      return st.end((err, res) => {
 | 
				
			||||||
        if (err) return done (err);
 | 
					        if (err) return done (err);
 | 
				
			||||||
        should(res.body).be.eql(this.res[options.httpStatus]);
 | 
					        should(res.body).be.eql(this.res[options.httpStatus]);
 | 
				
			||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {  // return object to do .end() manually
 | 
				
			||||||
      return st;
 | 
					      return st;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
import db from '../db';
 | 
					import db from '../db';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// script to load test db into dev db for a clean start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
db.connect('dev', () => {
 | 
					db.connect('dev', () => {
 | 
				
			||||||
  console.info('dropping data...');
 | 
					  console.info('dropping data...');
 | 
				
			||||||
  db.drop(() => {  // reset database
 | 
					  db.drop(() => {  // reset database
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user