diff --git a/api/sample.yaml b/api/sample.yaml
index e127d74..32bb6ed 100644
--- a/api/sample.yaml
+++ b/api/sample.yaml
@@ -1,6 +1,6 @@
/samples:
get:
- summary: TODO all samples in overview
+ summary: all samples in overview
description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /sample
@@ -10,7 +10,9 @@
content:
application/json:
schema:
- $ref: 'api.yaml#/components/schemas/Samples'
+ type: array
+ items:
+ $ref: 'api.yaml#/components/schemas/SampleRefs'
401:
$ref: 'api.yaml#/components/responses/401'
500:
@@ -39,7 +41,7 @@
500:
$ref: 'api.yaml#/components/responses/500'
put:
- summary: TODO add/change sample
+ summary: TODO change sample
description: 'Auth: basic, levels: write, maintain, dev, admin'
tags:
- /sample
@@ -88,10 +90,41 @@
$ref: 'api.yaml#/components/responses/404'
500:
$ref: 'api.yaml#/components/responses/500'
+
+/sample/new:
+ post:
+ summary: add sample
+ description: 'Auth: basic, levels: write, maintain, dev, admin'
+ tags:
+ - /sample
+ security:
+ - BasicAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: 'api.yaml#/components/schemas/Sample'
+ responses:
+ 200:
+ description: samples details
+ content:
+ application/json:
+ schema:
+ $ref: 'api.yaml#/components/schemas/SampleRefs'
+ 400:
+ $ref: 'api.yaml#/components/responses/400'
+ 401:
+ $ref: 'api.yaml#/components/responses/401'
+ 403:
+ $ref: 'api.yaml#/components/responses/403'
+ 500:
+ $ref: 'api.yaml#/components/responses/500'
+
/sample/notes/fields:
get:
summary: TODO list all existing field names for custom notes fields
- description: 'Auth: all, levels: write, maintain, dev, admin'
+ description: 'Auth: all, levels: read, write, maintain, dev, admin'
tags:
- /sample
responses:
diff --git a/api/schemas.yaml b/api/schemas.yaml
index 62b4690..a7aa0e2 100644
--- a/api/schemas.yaml
+++ b/api/schemas.yaml
@@ -14,16 +14,17 @@ Color:
example: black
SampleProperties:
properties:
- sample_number:
+ number:
type: string
+ example: Rng172
type:
type: string
+ example: granulate
batch:
type: string
- validated:
- type: boolean
+ example: 1560237365
-Samples:
+SampleRefs:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
- $ref: 'api.yaml#/components/schemas/Color'
@@ -41,17 +42,23 @@ Sample:
- $ref: 'api.yaml#/components/schemas/Color'
- $ref: 'api.yaml#/components/schemas/SampleProperties'
properties:
- material:
- $ref: 'api.yaml#/components/schemas/Material'
+ material_id:
+ allOf:
+ - $ref: 'api.yaml#/components/schemas/Id'
notes:
type: object
properties:
- comments:
+ comment:
type: string
sample_references:
type: array
items:
- $ref: 'api.yaml#/components/schemas/Id'
+ properties:
+ id:
+ $ref: 'api.yaml#/components/schemas/Id'
+ relation:
+ type: string
+ example: part to this sample
SampleDetail:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
@@ -63,7 +70,7 @@ SampleDetail:
notes:
type: object
properties:
- comments:
+ comment:
type: string
sample_references:
type: array
diff --git a/src/db.ts b/src/db.ts
index 090e275..f188468 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -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) => {
diff --git a/src/helpers/authorize.ts b/src/helpers/authorize.ts
index d3c7e75..e2f626a 100644
--- a/src/helpers/authorize.ts
+++ b/src/helpers/authorize.ts
@@ -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);
diff --git a/src/helpers/test.ts b/src/helpers/test.ts
index afd49dd..6c2fa72 100644
--- a/src/helpers/test.ts
+++ b/src/helpers/test.ts
@@ -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) {
diff --git a/src/index.ts b/src/index.ts
index 15bd504..63ca19e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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'));
diff --git a/src/models/note.ts b/src/models/note.ts
new file mode 100644
index 0000000..a13fd6a
--- /dev/null
+++ b/src/models/note.ts
@@ -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);
\ No newline at end of file
diff --git a/src/models/note_field.ts b/src/models/note_field.ts
new file mode 100644
index 0000000..86158e3
--- /dev/null
+++ b/src/models/note_field.ts
@@ -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);
\ No newline at end of file
diff --git a/src/models/sample.ts b/src/models/sample.ts
new file mode 100644
index 0000000..81dcc28
--- /dev/null
+++ b/src/models/sample.ts
@@ -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);
\ No newline at end of file
diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts
index c69538a..7b84c08 100644
--- a/src/routes/material.spec.ts
+++ b/src/routes/material.spec.ts
@@ -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');
diff --git a/src/routes/material.ts b/src/routes/material.ts
index c44afa7..5628fa6 100644
--- a/src/routes/material.ts
+++ b/src/routes/material.ts
@@ -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()));
});
});
diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts
new file mode 100644
index 0000000..857556c
--- /dev/null
+++ b/src/routes/sample.spec.ts
@@ -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
+ });
+ });
+ });
+});
diff --git a/src/routes/sample.ts b/src/routes/sample.ts
new file mode 100644
index 0000000..bbebaba
--- /dev/null
+++ b/src/routes/sample.ts
@@ -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);
+ })
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/src/routes/template.spec.ts b/src/routes/template.spec.ts
index 5ee4d1a..68b3d4a 100644
--- a/src/routes/template.spec.ts
+++ b/src/routes/template.spec.ts
@@ -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');
diff --git a/src/routes/template.ts b/src/routes/template.ts
index 7e4aee7..1e859cd 100644
--- a/src/routes/template.ts
+++ b/src/routes/template.ts
@@ -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;
\ No newline at end of file
diff --git a/src/routes/user.ts b/src/routes/user.ts
index c60dd7b..a0161f9 100644
--- a/src/routes/user.ts
+++ b/src/routes/user.ts
@@ -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,
You requested to reset your password.
Your new password is:
' + newPass + '
If you did not request a password reset, talk to the sysadmin quickly!
Have a nice day.
The DFOP team', err => {
- if (err) next(err);
+ if (err) return next(err);
res.json({status: 'OK'});
});
});
diff --git a/src/routes/validate/id.ts b/src/routes/validate/id.ts
index 84024e9..5409993 100644
--- a/src/routes/validate/id.ts
+++ b/src/routes/validate/id.ts
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/src/routes/validate/material.ts b/src/routes/validate/material.ts
index c5ac005..54cd749 100644
--- a/src/routes/validate/material.ts
+++ b/src/routes/validate/material.ts
@@ -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,
diff --git a/src/routes/validate/note_field.ts b/src/routes/validate/note_field.ts
new file mode 100644
index 0000000..4892f22
--- /dev/null
+++ b/src/routes/validate/note_field.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/routes/validate/sample.ts b/src/routes/validate/sample.ts
new file mode 100644
index 0000000..d94cede
--- /dev/null
+++ b/src/routes/validate/sample.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/routes/validate/template.ts b/src/routes/validate/template.ts
index 6a0a23f..a279dce 100644
--- a/src/routes/validate/template.ts
+++ b/src/routes/validate/template.ts
@@ -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,
diff --git a/src/routes/validate/user.ts b/src/routes/validate/user.ts
index 4b1259a..150bf64 100644
--- a/src/routes/validate/user.ts
+++ b/src/routes/validate/user.ts
@@ -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,
diff --git a/src/test/db.json b/src/test/db.json
index d1bca35..2d8a7d0 100644
--- a/src/test/db.json
+++ b/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"
+ }
]
}
}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 8bbe445..b43a5fb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,8 @@
"sourceMap": true,
"esModuleInterop": true,
"resolveJsonModule": true,
+ "incremental": true,
+ "diagnostics": true,
"typeRoots": [
"src/customTypings",
"node_modules/@types"