implemented first /sample methods
This commit is contained in:
		@@ -37,7 +37,7 @@ export default class db {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // connect to db
 | 
			
		||||
    mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true}, err => {
 | 
			
		||||
    mongoose.connect(connectionString, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, connectTimeoutMS: 10000}, err => {
 | 
			
		||||
      if (err) done(err);
 | 
			
		||||
    });
 | 
			
		||||
    mongoose.connection.on('error', console.error.bind(console, 'connection error:'));
 | 
			
		||||
@@ -92,7 +92,9 @@ export default class db {
 | 
			
		||||
    Object.keys(json.collections).forEach(collectionName => {  // create each collection
 | 
			
		||||
      for(let i in json.collections[collectionName]) {  // convert $oid fields to actual ObjectIds
 | 
			
		||||
        Object.keys(json.collections[collectionName][i]).forEach(key => {
 | 
			
		||||
          json.collections[collectionName][i][key] = json.collections[collectionName][i][key].hasOwnProperty('$oid') ? mongoose.Types.ObjectId(json.collections[collectionName][i][key].$oid) : json.collections[collectionName][i][key];
 | 
			
		||||
          if (json.collections[collectionName][i][key] !== null && json.collections[collectionName][i][key].hasOwnProperty('$oid')) {
 | 
			
		||||
            json.collections[collectionName][i][key] = mongoose.Types.ObjectId(json.collections[collectionName][i][key].$oid);
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      this.state.db.createCollection(collectionName, (err, collection) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import UserModel from '../models/user';
 | 
			
		||||
 | 
			
		||||
module.exports = async (req, res, next) => {
 | 
			
		||||
  let givenMethod = '';  // authorization method given by client, basic taken preferred
 | 
			
		||||
  let user = {name: '', level: ''};               // user object
 | 
			
		||||
  let user = {name: '', level: '', id: ''};               // user object
 | 
			
		||||
 | 
			
		||||
  // test authentications
 | 
			
		||||
  const userBasic = await basic(req, next);
 | 
			
		||||
@@ -45,7 +45,8 @@ module.exports = async (req, res, next) => {
 | 
			
		||||
  req.authDetails = {
 | 
			
		||||
    method: givenMethod,
 | 
			
		||||
    username: user.name,
 | 
			
		||||
    level: user.level
 | 
			
		||||
    level: user.level,
 | 
			
		||||
    id: user.id
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  next();
 | 
			
		||||
@@ -57,12 +58,12 @@ function basic (req, next): any {  // checks basic auth and returns changed user
 | 
			
		||||
    const auth = basicAuth(req);
 | 
			
		||||
    if (auth !== undefined) {  // basic auth available
 | 
			
		||||
      UserModel.find({name: auth.name}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        if (data.length === 1) {  // one user found
 | 
			
		||||
          bcrypt.compare(auth.pass, data[0].pass, (err, res) => {  // check password
 | 
			
		||||
            if (err) next(err);
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            if (res === true) {
 | 
			
		||||
              resolve({level: data[0].level, name: data[0].name});
 | 
			
		||||
              resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
              resolve(null);
 | 
			
		||||
@@ -84,9 +85,9 @@ function key (req, next): any {  // checks API key and returns changed user obje
 | 
			
		||||
  return new Promise(resolve => {
 | 
			
		||||
    if (req.query.key !== undefined) {
 | 
			
		||||
      UserModel.find({key: req.query.key}).lean().exec( (err, data: any) => {  // find user
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        if (data.length === 1) {  // one user found
 | 
			
		||||
          resolve({level: data[0].level, name: data[0].name});
 | 
			
		||||
          resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString()});
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          resolve(null);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ export default class TestHelper {
 | 
			
		||||
    401: {status: 'Unauthorized'},
 | 
			
		||||
    403: {status: 'Forbidden'},
 | 
			
		||||
    404: {status: 'Not found'},
 | 
			
		||||
    500: {status: 'Internal server error'}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static before (done) {
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,10 @@ app.use(require('./helpers/authorize'));  // handle authentication
 | 
			
		||||
 | 
			
		||||
// require routes
 | 
			
		||||
app.use('/', require('./routes/root'));
 | 
			
		||||
app.use('/', require('./routes/user'));
 | 
			
		||||
app.use('/', require('./routes/sample'));
 | 
			
		||||
app.use('/', require('./routes/material'));
 | 
			
		||||
app.use('/', require('./routes/template'));
 | 
			
		||||
app.use('/', require('./routes/user'));
 | 
			
		||||
 | 
			
		||||
// static files
 | 
			
		||||
app.use('/static', express.static('static'));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/models/note.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/models/note.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const NoteSchema = new mongoose.Schema({
 | 
			
		||||
  comment: String,
 | 
			
		||||
  sample_references: [{
 | 
			
		||||
    id: mongoose.Schema.Types.ObjectId,
 | 
			
		||||
    relation: String
 | 
			
		||||
  }],
 | 
			
		||||
  custom_fields: mongoose.Schema.Types.Mixed
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model('note', NoteSchema);
 | 
			
		||||
							
								
								
									
										8
									
								
								src/models/note_field.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/models/note_field.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const NoteFieldSchema = new mongoose.Schema({
 | 
			
		||||
  name: {type: String, index: {unique: true}},
 | 
			
		||||
  qty: Number
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model('note_field', NoteFieldSchema);
 | 
			
		||||
							
								
								
									
										18
									
								
								src/models/sample.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/models/sample.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
import MaterialModel from './material';
 | 
			
		||||
import NoteModel from './note';
 | 
			
		||||
import UserModel from './user';
 | 
			
		||||
 | 
			
		||||
const SampleSchema = new mongoose.Schema({
 | 
			
		||||
  number: {type: String, index: {unique: true}},
 | 
			
		||||
  type: String,
 | 
			
		||||
  color: String,
 | 
			
		||||
  batch: String,
 | 
			
		||||
  validated: Boolean,
 | 
			
		||||
  material_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialModel},
 | 
			
		||||
  note_id: {type: mongoose.Schema.Types.ObjectId, ref: NoteModel},
 | 
			
		||||
  user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default mongoose.model('sample', SampleSchema);
 | 
			
		||||
@@ -19,7 +19,7 @@ describe('/material', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.users.length);
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.materials.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
 | 
			
		||||
          should(material).have.property('_id').be.type('string');
 | 
			
		||||
@@ -47,7 +47,7 @@ describe('/material', () => {
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.users.length);
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.materials.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
 | 
			
		||||
          should(material).have.property('_id').be.type('string');
 | 
			
		||||
@@ -82,7 +82,7 @@ describe('/material', () => {
 | 
			
		||||
        url: '/material/100000000000000000000001',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]}
 | 
			
		||||
        res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns the right material for an API key', done => {
 | 
			
		||||
@@ -127,7 +127,7 @@ describe('/material', () => {
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {},
 | 
			
		||||
        res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}]}
 | 
			
		||||
        res: {_id: '100000000000000000000001', name: 'Stanyl TW 200 F8', supplier: 'DSM', group: 'PA46', mineral: 0, glass_fiber: 40, carbon_fiber: 0, numbers: [{color: 'black', number: 5514263423}, {color: 'natural', number: 5514263422}]}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('keeps unchanged properties', done => {
 | 
			
		||||
@@ -296,7 +296,6 @@ describe('/material', () => {
 | 
			
		||||
        req: {name: 'Crastin CE 2510', supplier: 'Du Pont', group: 'PBT', mineral: 0, glass_fiber: 30, carbon_fiber: 0, numbers: [{color: 'black', number: 5515798402}]}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        console.log(res.body);
 | 
			
		||||
        should(res.body).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers');
 | 
			
		||||
        should(res.body).have.property('_id').be.type('string');
 | 
			
		||||
        should(res.body).have.property('name', 'Crastin CE 2510');
 | 
			
		||||
@@ -324,7 +323,7 @@ describe('/material', () => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          console.log(data[0]);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', '__v');
 | 
			
		||||
          should(data[0]).have.property('_id');
 | 
			
		||||
          should(data[0]).have.property('name', 'Crastin CE 2510');
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ router.get('/materials', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find({}).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json(data.map(e => MaterialValidate.output(e)).filter(e => e !== null));  // validate all and filter null values from validation errors
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -20,8 +20,7 @@ router.get('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findById(req.params.id).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    console.log(data);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json(MaterialValidate.output(data));
 | 
			
		||||
    }
 | 
			
		||||
@@ -35,14 +34,14 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: material} = MaterialValidate.input(req.body, 'change');
 | 
			
		||||
  if(error !== undefined) {
 | 
			
		||||
  if (error) {
 | 
			
		||||
    res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (material.hasOwnProperty('name')) {
 | 
			
		||||
    MaterialModel.find({name: material.name}).lean().exec((err, data) => {
 | 
			
		||||
      if(err) next(err);
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data.length > 0 && data[0]._id != req.params.id) {
 | 
			
		||||
        res.status(400).json({status: 'Material name already taken'});
 | 
			
		||||
        return;
 | 
			
		||||
@@ -58,7 +57,7 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
 | 
			
		||||
  function f() {  // to resolve async
 | 
			
		||||
    MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => {
 | 
			
		||||
      if (err) next(err);
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data) {
 | 
			
		||||
        res.json(MaterialValidate.output(data));
 | 
			
		||||
      }
 | 
			
		||||
@@ -73,7 +72,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json({status: 'OK'})
 | 
			
		||||
    }
 | 
			
		||||
@@ -88,20 +87,20 @@ router.post('/material/new', (req, res, next) => {
 | 
			
		||||
 | 
			
		||||
  // validate input
 | 
			
		||||
  const {error, value: material} = MaterialValidate.input(req.body, 'new');
 | 
			
		||||
  if(error !== undefined) {
 | 
			
		||||
  if (error) {
 | 
			
		||||
    res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  MaterialModel.find({name: material.name}).lean().exec((err, data) => {
 | 
			
		||||
    if(err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data.length > 0) {
 | 
			
		||||
      res.status(400).json({status: 'Material name already taken'});
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    new MaterialModel(material).save((err, data) => {
 | 
			
		||||
      if(err) next(err);
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      res.json(MaterialValidate.output(data.toObject()));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										336
									
								
								src/routes/sample.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/routes/sample.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,336 @@
 | 
			
		||||
import should from 'should/as-function';
 | 
			
		||||
import SampleModel from '../models/sample';
 | 
			
		||||
import NoteModel from '../models/note';
 | 
			
		||||
import NoteFieldModel from '../models/note_field';
 | 
			
		||||
import TestHelper from "../helpers/test";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
describe('/sample', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  before(done => TestHelper.before(done));
 | 
			
		||||
  beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
 | 
			
		||||
  describe('GET /samples', () => {
 | 
			
		||||
    it('returns all samples', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/samples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.samples.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
			
		||||
          should(material).have.property('_id').be.type('string');
 | 
			
		||||
          should(material).have.property('number').be.type('string');
 | 
			
		||||
          should(material).have.property('type').be.type('string');
 | 
			
		||||
          should(material).have.property('color').be.type('string');
 | 
			
		||||
          should(material).have.property('batch').be.type('string');
 | 
			
		||||
          should(material).have.property('material_id').be.type('string');
 | 
			
		||||
          should(material).have.property('note_id');
 | 
			
		||||
          should(material).have.property('user_id').be.type('string');
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('works with an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/samples',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.samples.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('_id', 'number', 'type', 'color', 'batch', 'material_id', 'note_id', 'user_id');
 | 
			
		||||
          should(material).have.property('_id').be.type('string');
 | 
			
		||||
          should(material).have.property('number').be.type('string');
 | 
			
		||||
          should(material).have.property('type').be.type('string');
 | 
			
		||||
          should(material).have.property('color').be.type('string');
 | 
			
		||||
          should(material).have.property('batch').be.type('string');
 | 
			
		||||
          should(material).have.property('material_id').be.type('string');
 | 
			
		||||
          should(material).have.property('note_id');
 | 
			
		||||
          should(material).have.property('user_id').be.type('string');
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/samples',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /sample/new', () => {
 | 
			
		||||
    it('returns the right sample', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        should(res.body).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id');
 | 
			
		||||
        should(res.body).have.property('_id').be.type('string');
 | 
			
		||||
        should(res.body).have.property('number', 'Rng172');
 | 
			
		||||
        should(res.body).have.property('color', 'black');
 | 
			
		||||
        should(res.body).have.property('type', 'granulate');
 | 
			
		||||
        should(res.body).have.property('batch', '1560237365');
 | 
			
		||||
        should(res.body).have.property('material_id', '100000000000000000000001');
 | 
			
		||||
        should(res.body).have.property('note_id').be.type('string');
 | 
			
		||||
        should(res.body).have.property('user_id', '000000000000000000000002');
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('stores the sample', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
 | 
			
		||||
      }).end(err => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        SampleModel.find({number: 'Rng172'}).lean().exec((err, data: any) => {
 | 
			
		||||
          if (err) return done (err);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', '__v');
 | 
			
		||||
          should(data[0]).have.property('_id');
 | 
			
		||||
          should(data[0]).have.property('number', 'Rng172');
 | 
			
		||||
          should(data[0]).have.property('color', 'black');
 | 
			
		||||
          should(data[0]).have.property('type', 'granulate');
 | 
			
		||||
          should(data[0]).have.property('batch', '1560237365');
 | 
			
		||||
          should(data[0].material_id.toString()).be.eql('100000000000000000000001');
 | 
			
		||||
          should(data[0].user_id.toString()).be.eql('000000000000000000000002');
 | 
			
		||||
          should(data[0]).have.property('note_id');
 | 
			
		||||
          NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => {
 | 
			
		||||
            if (err) return done (err);
 | 
			
		||||
            should(data).have.property('_id');
 | 
			
		||||
            should(data).have.property('comment', 'Testcomment');
 | 
			
		||||
            should(data).have.property('sample_references');
 | 
			
		||||
            should(data.sample_references).have.lengthOf(1);
 | 
			
		||||
            should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003');
 | 
			
		||||
            should(data.sample_references[0]).have.property('relation', 'part to this sample');
 | 
			
		||||
            done();
 | 
			
		||||
          });
 | 
			
		||||
        })
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('stores the custom fields', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [], custom_fields: {field1: 'a', field2: 'b', 'not allowed for new applications': true}}}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done (err);
 | 
			
		||||
        NoteModel.findById(res.body.note_id).lean().exec((err, data: any) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.property('_id');
 | 
			
		||||
          should(data).have.property('comment', 'Testcomment');
 | 
			
		||||
          should(data).have.property('sample_references').have.lengthOf(0);
 | 
			
		||||
          should(data).have.property('custom_fields');
 | 
			
		||||
          should(data.custom_fields).have.property('field1', 'a');
 | 
			
		||||
          should(data.custom_fields).have.property('field2', 'b');
 | 
			
		||||
          should(data.custom_fields).have.property('not allowed for new applications', true);
 | 
			
		||||
          NoteFieldModel.find({name: 'field1'}).lean().exec((err, data) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.property('qty', 1);
 | 
			
		||||
            NoteFieldModel.find({name: 'field2'}).lean().exec((err, data) => {
 | 
			
		||||
              if (err) return done(err);
 | 
			
		||||
              should(data).have.lengthOf(1);
 | 
			
		||||
              should(data[0]).have.property('qty', 1);
 | 
			
		||||
              NoteFieldModel.find({name: 'not allowed for new applications'}).lean().exec((err, data) => {
 | 
			
		||||
                if (err) return done(err);
 | 
			
		||||
                should(data).have.lengthOf(1);
 | 
			
		||||
                should(data[0]).have.property('qty', 3);
 | 
			
		||||
                done();
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a color not defined for the material', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'green', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Color not available for material'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unknown material id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '000000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Material not available'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a sample number in use', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: '1', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Sample number already taken'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an invalid sample reference', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '000000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Sample reference not available'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a missing color', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a missing sample number', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a missing type', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a missing batch', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a missing material id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an invalid material id', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 400,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '10000000000h000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}},
 | 
			
		||||
        res: {status: 'Invalid body format'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {key: 'janedoe'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects requests from a read user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        auth: {basic: 'user'},
 | 
			
		||||
        httpStatus: 403,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/sample/new',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {number: 'Rng172', color: 'black', type: 'granulate', batch: '1560237365', material_id: '100000000000000000000001', notes: {comment: 'Testcomment', sample_references: [{id: '400000000000000000000003', relation: 'part to this sample'}]}}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('GET /sample/notes/fields', () => {
 | 
			
		||||
    it('returns all fields', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/sample/notes/fields',
 | 
			
		||||
        auth: {basic: 'user'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.note_fields.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('name', 'qty');
 | 
			
		||||
          should(material).have.property('qty').be.type('number');
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('works with an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/sample/notes/fields',
 | 
			
		||||
        auth: {key: 'user'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        const json = require('../test/db.json');
 | 
			
		||||
        should(res.body).have.lengthOf(json.collections.note_fields.length);
 | 
			
		||||
        should(res.body).matchEach(material => {
 | 
			
		||||
          should(material).have.only.keys('name', 'qty');
 | 
			
		||||
          should(material).have.property('qty').be.type('number');
 | 
			
		||||
        });
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects unauthorized requests', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/sample/notes/fields',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										109
									
								
								src/routes/sample.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/routes/sample.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
import express from 'express';
 | 
			
		||||
 | 
			
		||||
import SampleValidate from './validate/sample';
 | 
			
		||||
import NoteFieldValidate from './validate/note_field';
 | 
			
		||||
import SampleModel from '../models/sample'
 | 
			
		||||
import MaterialModel from '../models/material';
 | 
			
		||||
import NoteModel from '../models/note';
 | 
			
		||||
import NoteFieldModel from '../models/note_field';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/samples', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  SampleModel.find({}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json(data.map(e => SampleValidate.output(e)).filter(e => e !== null));  // validate all and filter null values from validation errors
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
router.post('/sample/new', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  const {error, value: sample} = SampleValidate.input(req.body, 'new');
 | 
			
		||||
  if (error) {
 | 
			
		||||
    return res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  MaterialModel.findById(sample.material_id).lean().exec((err, data: any) => {  // validate material_id
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data) {  // could not find material_id
 | 
			
		||||
      return res.status(400).json({status: 'Material not available'});
 | 
			
		||||
    }
 | 
			
		||||
    if (!data.numbers.find(e => e.color === sample.color)) {  // color for material not specified
 | 
			
		||||
      return res.status(400).json({status: 'Color not available for material'});
 | 
			
		||||
    }
 | 
			
		||||
    SampleModel.findOne({number: sample.number}).lean().exec((err, data) => {  // validate sample number
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data) {  // found entry with sample number
 | 
			
		||||
        return res.status(400).json({status: 'Sample number already taken'});
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (sample.notes.sample_references.length > 0) {  // validate sample_references
 | 
			
		||||
        let referencesCount = sample.notes.sample_references.length;
 | 
			
		||||
        sample.notes.sample_references.forEach(reference => {
 | 
			
		||||
          SampleModel.findById(reference.id).lean().exec((err, data) => {
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            if (!data) {
 | 
			
		||||
              return res.status(400).json({status: 'Sample reference not available'});
 | 
			
		||||
            }
 | 
			
		||||
            referencesCount --;
 | 
			
		||||
            if (referencesCount <= 0) {
 | 
			
		||||
              f();
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        f();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (sample.notes.hasOwnProperty('custom_fields') && Object.keys(sample.notes.custom_fields).length > 0) {
 | 
			
		||||
        customFieldsAdd(Object.keys(sample.notes.custom_fields));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function f() {  // to resolve async
 | 
			
		||||
        new NoteModel(sample.notes).save((err, data) => {
 | 
			
		||||
          if (err) return next(err);
 | 
			
		||||
          delete sample.notes;
 | 
			
		||||
          sample.note_id = data._id;
 | 
			
		||||
          sample.user_id = req.authDetails.id;
 | 
			
		||||
          new SampleModel(sample).save((err, data) => {
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            res.json(SampleValidate.output(data.toObject()));
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/sample/notes/fields', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
			
		||||
 | 
			
		||||
  NoteFieldModel.find({}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json(data.map(e => NoteFieldValidate.output(e)).filter(e => e !== null));  // validate all and filter null values from validation errors
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function customFieldsAdd (fields) {
 | 
			
		||||
  fields.forEach(field => {
 | 
			
		||||
    NoteFieldModel.findOneAndUpdate({name: field}, {$inc: {qty: 1}}).lean().exec((err, data) => {  // check if field exists
 | 
			
		||||
      if (err) return console.error(err);
 | 
			
		||||
      if (!data) {  // new field
 | 
			
		||||
        new NoteFieldModel({name: field, qty: 1}).save(err => {
 | 
			
		||||
          if (err) return console.error(err);
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -172,7 +172,6 @@ describe('/template', () => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          TemplateTreatmentModel.find({name: 'heat aging'}).lean().exec((err, data:any) => {
 | 
			
		||||
            if (err) return done(err);
 | 
			
		||||
            console.log(data);
 | 
			
		||||
            should(data).have.lengthOf(1);
 | 
			
		||||
            should(data[0]).have.only.keys('_id', 'name', 'parameters', '__v');
 | 
			
		||||
            should(data[0]).have.property('name', 'heat aging');
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next
 | 
			
		||||
    if (err) next (err);
 | 
			
		||||
    const templateState = data? 'change': 'new';
 | 
			
		||||
    const {error, value: template} = TemplateValidate.input(req.body, templateState);
 | 
			
		||||
    if(error !== undefined) {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -64,7 +64,7 @@ router.put('/template/:collection(measurement|treatment)/:name', (req, res, next
 | 
			
		||||
 | 
			
		||||
    function f() {  // to resolve async
 | 
			
		||||
      collectionModel.findOneAndUpdate({name: req.params.name}, template, {new: true, upsert: true}).lean().exec((err, data) => {
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        res.json(TemplateValidate.output(data));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@@ -76,7 +76,7 @@ router.delete('/template/:collection(measurement|treatment)/:name', (req, res, n
 | 
			
		||||
 | 
			
		||||
  (req.params.collection === 'treatment' ? TemplateTreatmentModel : TemplateMeasurementModel)
 | 
			
		||||
    .findOneAndDelete({name: req.params.name}).lean().exec((err, data) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json({status: 'OK'})
 | 
			
		||||
    }
 | 
			
		||||
@@ -87,5 +87,4 @@ router.delete('/template/:collection(measurement|treatment)/:name', (req, res, n
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
@@ -27,7 +27,7 @@ router.get('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  UserModel.findOne({name: username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json(UserValidate.output(data));  // validate all and filter null values from validation errors
 | 
			
		||||
    }
 | 
			
		||||
@@ -46,7 +46,7 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
			
		||||
    username = req.params.username;
 | 
			
		||||
  }
 | 
			
		||||
  const {error, value: user} = UserValidate.input(req.body, 'change' + (req.authDetails.level === 'admin'? 'admin' : ''));
 | 
			
		||||
  if(error !== undefined) {
 | 
			
		||||
  if (error) {
 | 
			
		||||
    res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -58,14 +58,14 @@ router.put('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  // thi
 | 
			
		||||
  // check that user does not already exist if new name was specified
 | 
			
		||||
  if (user.hasOwnProperty('name') && user.name !== username) {
 | 
			
		||||
    UserModel.find({name: user.name}).lean().exec(  (err, data:any) => {
 | 
			
		||||
      if (err) next(err);
 | 
			
		||||
      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) => {
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        if (data) {
 | 
			
		||||
          res.json(UserValidate.output(data));
 | 
			
		||||
        }
 | 
			
		||||
@@ -77,7 +77,7 @@ 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) next(err);
 | 
			
		||||
      if (err) return next(err);
 | 
			
		||||
      if (data) {
 | 
			
		||||
        res.json(UserValidate.output(data));  // validate all and filter null values from validation errors
 | 
			
		||||
      }
 | 
			
		||||
@@ -98,7 +98,7 @@ router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  //
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  UserModel.findOneAndDelete({name: username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data) {
 | 
			
		||||
      res.json({status: 'OK'})
 | 
			
		||||
    }
 | 
			
		||||
@@ -109,11 +109,10 @@ router.delete('/user:username([/](?!key|new).?*|/?)', (req, res, next) => {  //
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get('/user/key', (req, res, next) => {
 | 
			
		||||
  console.log('hmm');
 | 
			
		||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'basic')) return;
 | 
			
		||||
 | 
			
		||||
  UserModel.findOne({name: req.authDetails.username}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json({key: data.key});
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -123,14 +122,14 @@ router.post('/user/new', (req, res, next) => {
 | 
			
		||||
 | 
			
		||||
  // validate input
 | 
			
		||||
  const {error, value: user} = UserValidate.input(req.body, 'new');
 | 
			
		||||
  if(error !== undefined) {
 | 
			
		||||
  if (error) {
 | 
			
		||||
    res.status(400).json({status: 'Invalid body format'});
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // check that user does not already exist
 | 
			
		||||
  UserModel.find({name: user.name}).lean().exec(  (err, data:any) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data.length > 0  || UserValidate.isSpecialName(user.name)) {
 | 
			
		||||
      res.status(400).json({status: 'Username already taken'});
 | 
			
		||||
      return;
 | 
			
		||||
@@ -140,7 +139,7 @@ router.post('/user/new', (req, res, next) => {
 | 
			
		||||
    bcrypt.hash(user.pass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
      user.pass = hash;
 | 
			
		||||
      new UserModel(user).save((err, data) => {  // store user
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        res.json(UserValidate.output(data.toObject()));
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
@@ -150,15 +149,15 @@ router.post('/user/new', (req, res, next) => {
 | 
			
		||||
router.post('/user/passreset', (req, res, next) => {
 | 
			
		||||
  // check if user/email combo exists
 | 
			
		||||
  UserModel.find({name: req.body.name, email: req.body.email}).lean().exec( (err, data: any) => {
 | 
			
		||||
    if (err) next(err);
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (data.length === 1) {  // it exists
 | 
			
		||||
      const newPass = Math.random().toString(36).substring(2);
 | 
			
		||||
      bcrypt.hash(newPass, 10, (err, hash) => {  // password hashing
 | 
			
		||||
        if (err) next(err);
 | 
			
		||||
        if (err) return next(err);
 | 
			
		||||
        UserModel.findByIdAndUpdate(data[0]._id, {pass: hash}, err => {  // write new password
 | 
			
		||||
          if (err) next(err);
 | 
			
		||||
          if (err) return next(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) next(err);
 | 
			
		||||
            if (err) return next(err);
 | 
			
		||||
            res.json({status: 'OK'});
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,16 @@ export default class IdValidate {
 | 
			
		||||
    return this.id.validate(id).error === undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static parameter() {  // :id url parameter
 | 
			
		||||
  static parameter () {  // :id url parameter
 | 
			
		||||
    return ':id([0-9a-f]{24})';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static stringify (data) {
 | 
			
		||||
    Object.keys(data).forEach(key => {
 | 
			
		||||
      if (data[key] !== null && data[key].hasOwnProperty('_bsontype') && data[key]._bsontype === 'ObjectID') {
 | 
			
		||||
        data[key] = data[key].toString();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -66,7 +66,7 @@ export default class MaterialValidate {  // validate input for material
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
			
		||||
    data._id = data._id.toString();
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.material.name,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/routes/validate/note_field.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/routes/validate/note_field.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import joi from '@hapi/joi';
 | 
			
		||||
 | 
			
		||||
export default class NoteFieldValidate {
 | 
			
		||||
  private static note_field = {
 | 
			
		||||
    name: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    qty: joi.number()
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static output (data) {
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      name: this.note_field.name,
 | 
			
		||||
      qty: this.note_field.qty
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								src/routes/validate/sample.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/routes/validate/sample.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
import joi from '@hapi/joi';
 | 
			
		||||
 | 
			
		||||
import IdValidate from './id';
 | 
			
		||||
 | 
			
		||||
export default class SampleValidate {
 | 
			
		||||
  private static sample = {
 | 
			
		||||
    number: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    color: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    type: joi.string()
 | 
			
		||||
      .max(128),
 | 
			
		||||
 | 
			
		||||
    batch: joi.string()
 | 
			
		||||
      .max(128)
 | 
			
		||||
      .allow(''),
 | 
			
		||||
 | 
			
		||||
    notes: joi.object({
 | 
			
		||||
      comment: joi.string()
 | 
			
		||||
        .max(512),
 | 
			
		||||
 | 
			
		||||
      sample_references: joi.array()
 | 
			
		||||
        .items(joi.object({
 | 
			
		||||
          id: IdValidate.get(),
 | 
			
		||||
 | 
			
		||||
          relation: joi.string()
 | 
			
		||||
            .max(128)
 | 
			
		||||
        })),
 | 
			
		||||
 | 
			
		||||
      custom_fields: joi.object()
 | 
			
		||||
        .pattern(/.*/, joi.alternatives()
 | 
			
		||||
          .try(
 | 
			
		||||
            joi.string().max(128),
 | 
			
		||||
            joi.number(),
 | 
			
		||||
            joi.boolean(),
 | 
			
		||||
            joi.date()
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
    })
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static input (data, param) {  // validate data, param: new(everything required)/change(available attributes are validated)
 | 
			
		||||
    if (param === 'new') {
 | 
			
		||||
      return joi.object({
 | 
			
		||||
        number: this.sample.number.required(),
 | 
			
		||||
        color: this.sample.color.required(),
 | 
			
		||||
        type: this.sample.type.required(),
 | 
			
		||||
        batch: this.sample.batch.required(),
 | 
			
		||||
        material_id: IdValidate.get().required(),
 | 
			
		||||
        notes: this.sample.notes.required()
 | 
			
		||||
      }).validate(data);
 | 
			
		||||
    }
 | 
			
		||||
    else if (param === 'change') {
 | 
			
		||||
      return{error: 'Not implemented!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return{error: 'No parameter specified!', value: {}};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      number: this.sample.number,
 | 
			
		||||
      color: this.sample.color,
 | 
			
		||||
      type: this.sample.type,
 | 
			
		||||
      batch: this.sample.batch,
 | 
			
		||||
      material_id: IdValidate.get(),
 | 
			
		||||
      note_id: IdValidate.get().allow(null),
 | 
			
		||||
      user_id: IdValidate.get()
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -48,7 +48,7 @@ export default class TemplateValidate {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
			
		||||
    data._id = data._id.toString();
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.template.name,
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ export default class UserValidate {  // validate input for user
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {  // validate output from database for needed properties, strip everything else
 | 
			
		||||
    data._id = data._id.toString();
 | 
			
		||||
    data = IdValidate.stringify(data);
 | 
			
		||||
    const {value, error} = joi.object({
 | 
			
		||||
      _id: IdValidate.get(),
 | 
			
		||||
      name: this.user.name,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										178
									
								
								src/test/db.json
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								src/test/db.json
									
									
									
									
									
								
							@@ -1,38 +1,93 @@
 | 
			
		||||
{
 | 
			
		||||
  "collections": {
 | 
			
		||||
    "users": [
 | 
			
		||||
    "samples": [
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000001"},
 | 
			
		||||
        "email": "user@bosch.com",
 | 
			
		||||
        "name": "user",
 | 
			
		||||
        "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
 | 
			
		||||
        "level": "read",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "Alpha I",
 | 
			
		||||
        "key": "000000000000000000001001",
 | 
			
		||||
        "_id": {"$oid":"400000000000000000000001"},
 | 
			
		||||
        "number": "1",
 | 
			
		||||
        "type": "granulate",
 | 
			
		||||
        "color": "black",
 | 
			
		||||
        "batch": "",
 | 
			
		||||
        "validated": true,
 | 
			
		||||
        "material_id": {"$oid":"100000000000000000000004"},
 | 
			
		||||
        "note_id": null,
 | 
			
		||||
        "user_id": {"$oid":"000000000000000000000002"},
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000002"},
 | 
			
		||||
        "email": "jane.doe@bosch.com",
 | 
			
		||||
        "name": "janedoe",
 | 
			
		||||
        "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
 | 
			
		||||
        "level": "write",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "Alpha I",
 | 
			
		||||
        "key": "000000000000000000001002",
 | 
			
		||||
        "_id": {"$oid":"400000000000000000000002"},
 | 
			
		||||
        "number": "21",
 | 
			
		||||
        "type": "granulate",
 | 
			
		||||
        "color": "natural",
 | 
			
		||||
        "batch": "1560237365",
 | 
			
		||||
        "validated": true,
 | 
			
		||||
        "material_id": {"$oid":"100000000000000000000001"},
 | 
			
		||||
        "note_id": {"$oid":"500000000000000000000001"},
 | 
			
		||||
        "user_id": {"$oid":"000000000000000000000002"},
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000003"},
 | 
			
		||||
        "email": "a.d.m.i.n@bosch.com",
 | 
			
		||||
        "name": "admin",
 | 
			
		||||
        "pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
 | 
			
		||||
        "level": "admin",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "",
 | 
			
		||||
        "key": "000000000000000000001003",
 | 
			
		||||
        "__v": "0"
 | 
			
		||||
        "_id": {"$oid":"400000000000000000000003"},
 | 
			
		||||
        "number": "33",
 | 
			
		||||
        "type": "part",
 | 
			
		||||
        "color": "black",
 | 
			
		||||
        "batch": "1704-005",
 | 
			
		||||
        "validated": false,
 | 
			
		||||
        "material_id": {"$oid":"100000000000000000000005"},
 | 
			
		||||
        "note_id": {"$oid":"500000000000000000000002"},
 | 
			
		||||
        "user_id": {"$oid":"000000000000000000000003"},
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"400000000000000000000004"},
 | 
			
		||||
        "number": "32",
 | 
			
		||||
        "type": "granulate",
 | 
			
		||||
        "color": "black",
 | 
			
		||||
        "batch": "1653000308",
 | 
			
		||||
        "validated": false,
 | 
			
		||||
        "material_id": {"$oid":"100000000000000000000005"},
 | 
			
		||||
        "note_id": {"$oid":"500000000000000000000003"},
 | 
			
		||||
        "user_id": {"$oid":"000000000000000000000003"},
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "notes": [
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"500000000000000000000001"},
 | 
			
		||||
        "comment": "Stoff gesperrt",
 | 
			
		||||
        "sample_references": [],
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"500000000000000000000002"},
 | 
			
		||||
        "comment": "",
 | 
			
		||||
        "sample_references": [{
 | 
			
		||||
          "id": "400000000000000000000004",
 | 
			
		||||
          "relation": "granulate to sample"
 | 
			
		||||
        }],
 | 
			
		||||
        "custom_fields": {
 | 
			
		||||
          "not allowed for new applications": true
 | 
			
		||||
        },
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"500000000000000000000003"},
 | 
			
		||||
        "comment": "",
 | 
			
		||||
        "sample_references": [{
 | 
			
		||||
          "id": "400000000000000000000003",
 | 
			
		||||
          "relation": "part to sample"
 | 
			
		||||
        }],
 | 
			
		||||
        "custom_fields": {
 | 
			
		||||
          "not allowed for new applications": true
 | 
			
		||||
        },
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "note_fields": [
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"600000000000000000000001"},
 | 
			
		||||
        "name": "not allowed for new applications",
 | 
			
		||||
        "qty": 2,
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "materials": [
 | 
			
		||||
@@ -48,6 +103,10 @@
 | 
			
		||||
          {
 | 
			
		||||
            "color": "black",
 | 
			
		||||
            "number": 5514263423
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "color": "natural",
 | 
			
		||||
            "number": 5514263422
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "__v": 0
 | 
			
		||||
@@ -83,6 +142,38 @@
 | 
			
		||||
        "numbers": [
 | 
			
		||||
        ],
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"100000000000000000000004"},
 | 
			
		||||
        "name": "Schulamid 66 GF 25 H",
 | 
			
		||||
        "supplier": "Schulmann",
 | 
			
		||||
        "group": "PA66",
 | 
			
		||||
        "mineral": 0,
 | 
			
		||||
        "glass_fiber": 25,
 | 
			
		||||
        "carbon_fiber": 0,
 | 
			
		||||
        "numbers": [
 | 
			
		||||
          {
 | 
			
		||||
            "color": "black",
 | 
			
		||||
            "number": 5513933405
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"100000000000000000000005"},
 | 
			
		||||
        "name": "Amodel A 1133 HS",
 | 
			
		||||
        "supplier": "Solvay",
 | 
			
		||||
        "group": "PPA",
 | 
			
		||||
        "mineral": 0,
 | 
			
		||||
        "glass_fiber": 33,
 | 
			
		||||
        "carbon_fiber": 0,
 | 
			
		||||
        "numbers": [
 | 
			
		||||
          {
 | 
			
		||||
            "color": "black",
 | 
			
		||||
            "number": 5514262406
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "treatment_templates": [
 | 
			
		||||
@@ -150,6 +241,41 @@
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "users": [
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000001"},
 | 
			
		||||
        "email": "user@bosch.com",
 | 
			
		||||
        "name": "user",
 | 
			
		||||
        "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
 | 
			
		||||
        "level": "read",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "Alpha I",
 | 
			
		||||
        "key": "000000000000000000001001",
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000002"},
 | 
			
		||||
        "email": "jane.doe@bosch.com",
 | 
			
		||||
        "name": "janedoe",
 | 
			
		||||
        "pass": "$2a$10$di26XKF63OG0V00PL1kSK.ceCcTxDExBMOg.jkHiCnXcY7cN7DlPi",
 | 
			
		||||
        "level": "write",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "Alpha I",
 | 
			
		||||
        "key": "000000000000000000001002",
 | 
			
		||||
        "__v": 0
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"000000000000000000000003"},
 | 
			
		||||
        "email": "a.d.m.i.n@bosch.com",
 | 
			
		||||
        "name": "admin",
 | 
			
		||||
        "pass": "$2a$10$i872o3qR5V3JnbDArD8Z.eDo.BNPDBaR7dUX9KSEtl9pUjLyucy2K",
 | 
			
		||||
        "level": "admin",
 | 
			
		||||
        "location": "Rng",
 | 
			
		||||
        "device_name": "",
 | 
			
		||||
        "key": "000000000000000000001003",
 | 
			
		||||
        "__v": "0"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user