diff --git a/api/api.yaml b/api/api.yaml index 0c17f4d..f890477 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -34,6 +34,7 @@ info:
  • 0: newly added/changed
  • 10: validated
  • + Bitbucket repository # TODO: Link to new documentation page diff --git a/api/material.yaml b/api/material.yaml index 5e8bc13..6a86b38 100644 --- a/api/material.yaml +++ b/api/material.yaml @@ -2,6 +2,7 @@ get: summary: lists all materials description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: returns only materials with status 10 # TODO: methods /materials/new|deleted tags: - /material responses: @@ -24,6 +25,7 @@ get: summary: get material details description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /material responses: @@ -42,6 +44,7 @@ put: summary: change material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: status is reset to 0 on any changes tags: - /material security: @@ -72,6 +75,7 @@ delete: summary: delete material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /material security: @@ -94,6 +98,7 @@ post: summary: add material description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: 'Adds status: 0 automatically' tags: - /material security: diff --git a/src/models/material.ts b/src/models/material.ts index 530f8f0..a5378e0 100644 --- a/src/models/material.ts +++ b/src/models/material.ts @@ -10,7 +10,8 @@ const MaterialSchema = new mongoose.Schema({ numbers: [{ color: String, number: Number - }] + }], + status: Number }); export default mongoose.model('material', MaterialSchema); \ No newline at end of file diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 59bdd4a..e7767de 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -2,6 +2,8 @@ import should from 'should/as-function'; import MaterialModel from '../models/material'; import TestHelper from "../test/helper"; // TODO: status +// TODO: numbers with color only (no number) +// TODO: deal with numbers with leading zeros describe('/material', () => { let server; @@ -19,7 +21,8 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.materials.length); + console.log(res.body); + should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).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 +50,7 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); const json = require('../test/db.json'); - should(res.body).have.lengthOf(json.collections.materials.length); + should(res.body).have.lengthOf(json.collections.materials.filter(e => e.status === 10).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'); @@ -136,8 +139,32 @@ describe('/material', () => { url: '/material/100000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {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}]} + req: {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}]} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_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}]}); + MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); + }); + }); + it('keeps only one unchanged property', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/material/100000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {name: 'Stanyl TW 200 F8'} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_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}]}); + MaterialModel.findById('100000000000000000000001').lean().exec((err, data) => { + if (err) return done(err); + should(data).have.property('status', 10); + done(); + }); }); }); it('changes the given properties', done => { @@ -155,8 +182,7 @@ describe('/material', () => { if (err) return done(err); data._id = data._id.toString(); data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}}); - should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], __v: 0} - ); + should(data).be.eql({_id: '100000000000000000000001', name: 'UltramidTKR4355G7_2', supplier: 'BASF', group: 'PA6/6T', mineral: '0', glass_fiber: '35', carbon_fiber: '0', numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: 0, __v: 0}); done(); }); }); @@ -268,7 +294,7 @@ describe('/material', () => { }); describe('DELETE /material/{id}', () => { - it('deletes the material', done => { + it('sets the status to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/material/100000000000000000000002', @@ -277,9 +303,12 @@ describe('/material', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); - MaterialModel.findById('100000000000000000000002').lean().exec((err, data) => { + MaterialModel.findById('100000000000000000000002').lean().exec((err, data: any) => { if (err) return done(err); - should(data).be.null(); + data._id = data._id.toString(); + data.numbers = data.numbers.map(e => {return {color: e.color, number: e.number}}); + should(data).be.eql({_id: '100000000000000000000002', name: 'Ultramid T KR 4355 G7', supplier: 'BASF', group: 'PA6/6T', mineral: 0, glass_fiber: 35, carbon_fiber: 0, numbers: [{color: 'black', number: 5514212901}, {color: 'signalviolet', number: 5514612901}], status: -1, __v: 0} + ); done(); }); }); @@ -372,7 +401,7 @@ describe('/material', () => { MaterialModel.find({name: 'Crastin CE 2510'}).lean().exec((err, data: any) => { if (err) return done (err); 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.only.keys('_id', 'name', 'supplier', 'group', 'mineral', 'glass_fiber', 'carbon_fiber', 'numbers', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('name', 'Crastin CE 2510'); should(data[0]).have.property('supplier', 'Du Pont'); @@ -380,6 +409,7 @@ describe('/material', () => { should(data[0]).have.property('mineral', '0'); should(data[0]).have.property('glass_fiber', '30'); should(data[0]).have.property('carbon_fiber', '0'); + should(data[0]).have.property('status', 0); should(data[0].numbers).have.lengthOf(0); done(); }); diff --git a/src/routes/material.ts b/src/routes/material.ts index fdb0c47..7601796 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -8,13 +8,14 @@ import IdValidate from './validate/id'; import res400 from './validate/res400'; import mongoose from 'mongoose'; +// TODO: remove f() for await const router = express.Router(); router.get('/materials', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; - MaterialModel.find({}).lean().exec((err, data) => { + MaterialModel.find({status: 10}).lean().exec((err, data) => { if (err) return next(err); res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors }); @@ -40,33 +41,24 @@ router.put('/material/' + IdValidate.parameter(), (req, res, next) => { const {error, value: material} = MaterialValidate.input(req.body, 'change'); if (error) return res400(error, res); - if (material.hasOwnProperty('name')) { - MaterialModel.find({name: material.name}).lean().exec((err, data) => { - 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; - } - else { - f(); - } - }); - } - else { - f(); - } + MaterialModel.findById(req.params.id).lean().exec(async (err, materialData: any) => { + if (!materialData) { + return res.status(404).json({status: 'Not found'}); + } + if (material.hasOwnProperty('name') && material.name !== materialData.name) { + if (!await nameCheck(material, res, next)) return; + } - function f() { // to resolve async - MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => { + // check for changes + if (!_.isEqual(_.pick(IdValidate.stringify(materialData), _.keys(material)), material)) { + material.status = 0; + } + + await MaterialModel.findByIdAndUpdate(req.params.id, material, {new: true}).lean().exec((err, data) => { if (err) return next(err); - if (data) { - res.json(MaterialValidate.output(data)); - } - else { - res.status(404).json({status: 'Not found'}); - } + res.json(MaterialValidate.output(data)); }); - } + }); }); router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { @@ -78,7 +70,7 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { if (data.length) { return res.status(400).json({status: 'Material still in use'}); } - MaterialModel.findByIdAndDelete(req.params.id).lean().exec((err, data) => { + MaterialModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec((err, data) => { if (err) return next(err); if (data) { res.json({status: 'OK'}); @@ -90,26 +82,34 @@ router.delete('/material/' + IdValidate.parameter(), (req, res, next) => { }); }); -router.post('/material/new', (req, res, next) => { +router.post('/material/new', async (req, res, next) => { if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; // validate input const {error, value: material} = MaterialValidate.input(req.body, 'new'); if (error) return res400(error, res); - MaterialModel.find({name: material.name}).lean().exec((err, data) => { - if (err) return next(err); - if (data.length > 0) { - res.status(400).json({status: 'Material name already taken'}); - return; - } + if (!await nameCheck(material, res, next)) return; - new MaterialModel(material).save((err, data) => { - if (err) return next(err); - res.json(MaterialValidate.output(data.toObject())); - }); + material.status = 0; + await new MaterialModel(material).save((err, data) => { + if (err) return next(err); + res.json(MaterialValidate.output(data.toObject())); }); }); -module.exports = router; \ No newline at end of file +module.exports = router; + + +async function nameCheck (material, res, next) { // check if name was already taken + const materialData = await MaterialModel.findOne({name: material.name}).lean().exec().catch(err => {next(err); return false;}) as any; + if (materialData instanceof Error) { + return false; + } + if (materialData) { // could not find material_id + res.status(400).json({status: 'Material name already taken'}); + return false; + } + return true; +} \ No newline at end of file diff --git a/src/routes/template.ts b/src/routes/template.ts index 2c0277c..5f1477c 100644 --- a/src/routes/template.ts +++ b/src/routes/template.ts @@ -6,6 +6,7 @@ import TemplateTreatmentModel from '../models/treatment_template'; import TemplateMeasurementModel from '../models/measurement_template'; import res400 from './validate/res400'; +// TODO: remove f() for await const router = express.Router(); diff --git a/src/test/db.json b/src/test/db.json index a5a347a..6ac2156 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -128,6 +128,7 @@ "number": 5514263422 } ], + "status": 10, "__v": 0 }, { @@ -148,6 +149,7 @@ "number": 5514612901 } ], + "status": 10, "__v": 0 }, { @@ -160,6 +162,7 @@ "carbon_fiber": 0, "numbers": [ ], + "status": 10, "__v": 0 }, { @@ -176,6 +179,7 @@ "number": 5513933405 } ], + "status": 10, "__v": 0 }, { @@ -192,6 +196,24 @@ "number": 5514262406 } ], + "status": 10, + "__v": 0 + }, + { + "_id": {"$oid":"100000000000000000000006"}, + "name": "PK-HM natural (4773)", + "supplier": "Akro-Plastic", + "group": "PK", + "mineral": 0, + "glass_fiber": 0, + "carbon_fiber": 0, + "numbers": [ + { + "color": "natural", + "number": 10000000 + } + ], + "status": -1, "__v": 0 } ],