diff --git a/api/condition.yaml b/api/condition.yaml index 696aa4d..f924707 100644 --- a/api/condition.yaml +++ b/api/condition.yaml @@ -4,6 +4,7 @@ get: summary: condition by id description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /condition responses: @@ -24,6 +25,7 @@ put: summary: change condition description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user' + x-doc: status is reset to 0 on any changes tags: - /condition security: @@ -61,6 +63,7 @@ delete: summary: delete condition description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /condition security: @@ -83,6 +86,7 @@ post: summary: add condition description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to reference samples created by another user' + x-doc: 'Adds status: 0 automatically' tags: - /condition security: diff --git a/api/measurement.yaml b/api/measurement.yaml index d9fcd40..2882883 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -4,6 +4,7 @@ get: summary: measurement values by id description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /measurement responses: @@ -57,6 +58,7 @@ delete: summary: delete measurement description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: sets status to -1 tags: - /measurement security: diff --git a/api/sample.yaml b/api/sample.yaml index 4d2817b..9539053 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -2,6 +2,7 @@ get: summary: all samples in overview description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: returns only samples with status 10 # TODO: methods /samples/new|deleted tags: - /sample responses: @@ -23,6 +24,7 @@ get: summary: TODO sample details description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: status handling (accessible (only for maintain/admin))? # TODO tags: - /sample responses: @@ -43,6 +45,7 @@ put: summary: change sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' + x-doc: status is reset to 0 on any changes tags: - /sample security: @@ -73,6 +76,7 @@ delete: summary: delete sample description: 'Auth: basic, levels: write, maintain, dev, admin
Only maintain and admin are allowed to edit samples created by another user' + x-doc: sets status to -1, notes and references to this sample are also kept, only note_fields are updated accordingly tags: - /sample security: @@ -95,6 +99,7 @@ post: summary: add sample description: 'Auth: basic, levels: write, maintain, dev, admin' + x-doc: 'Adds status: 0 automatically' tags: - /sample security: @@ -125,6 +130,7 @@ get: summary: list all existing field names for custom notes fields description: 'Auth: all, levels: read, write, maintain, dev, admin' + x-doc: integrity has to be ensured # TODO: implement mechanism to regularly check note_fields tags: - /sample responses: diff --git a/src/models/sample.ts b/src/models/sample.ts index 81dcc28..9e5353b 100644 --- a/src/models/sample.ts +++ b/src/models/sample.ts @@ -9,10 +9,10 @@ const SampleSchema = new mongoose.Schema({ 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} + user_id: {type: mongoose.Schema.Types.ObjectId, ref: UserModel}, + status: Number }); export default mongoose.model('sample', SampleSchema); \ No newline at end of file diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index d74703d..99f6344 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -5,7 +5,6 @@ import NoteFieldModel from '../models/note_field'; import TestHelper from "../test/helper"; // TODO: generate sample number // TODO: think again which parameters are required at POST -// TODO: status describe('/sample', () => { let server; @@ -23,7 +22,7 @@ describe('/sample', () => { }).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).have.lengthOf(json.collections.samples.filter(e => e.status === 10).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'); @@ -47,7 +46,7 @@ describe('/sample', () => { }).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).have.lengthOf(json.collections.samples.filter(e => e.status === 10).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'); @@ -88,8 +87,41 @@ describe('/sample', () => { url: '/sample/400000000000000000000001', auth: {basic: 'janedoe'}, httpStatus: 200, - req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}}, - res: {_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'} + req: {number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', notes: {}} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}); + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { + if (err) return done (err); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); + should(data).have.property('_id'); + should(data).have.property('number', '1'); + should(data).have.property('color', 'black'); + should(data).have.property('type', 'granulate'); + should(data).have.property('batch', ''); + should(data.material_id.toString()).be.eql('100000000000000000000004'); + should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', 10); + should(data).have.property('note_id', null); + done(); + }); + }); + }); + it('keeps only one unchanged parameter', done => { + TestHelper.request(server, done, { + method: 'put', + url: '/sample/400000000000000000000001', + auth: {basic: 'janedoe'}, + httpStatus: 200, + req: {type: 'granulate'} + }).end((err, res) => { + if (err) return done(err); + should(res.body).be.eql({_id: '400000000000000000000001', number: '1', type: 'granulate', color: 'black', batch: '', material_id: '100000000000000000000004', note_id: null, user_id: '000000000000000000000002'}); + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { + if (err) return done (err); + should(data).have.property('status', 10); + done(); + }); }); }); it('changes the given properties', done => { @@ -103,15 +135,15 @@ describe('/sample', () => { if (err) return done (err); SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { if (err) return done (err); - should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'validated', 'material_id', 'note_id', 'user_id', '__v'); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); should(data).have.property('_id'); should(data).have.property('number', '10'); should(data).have.property('color', 'signalviolet'); should(data).have.property('type', 'part'); should(data).have.property('batch', '114531'); - should(data).have.property('validated').be.type('boolean'); should(data.material_id.toString()).be.eql('100000000000000000000002'); should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', 0); should(data).have.property('note_id'); NoteModel.findById(data.note_id).lean().exec((err, data: any) => { if (err) return done (err); @@ -123,7 +155,7 @@ describe('/sample', () => { should(data.sample_references[0]).have.property('relation', 'part to this sample'); done(); }); - }) + }); }); }); it('adjusts the note_fields correctly', done => { @@ -315,7 +347,7 @@ describe('/sample', () => { }); describe('DELETE /sample/{id}', () => { - it('deletes the sample', done => { + it('sets the status to deleted', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000001', @@ -324,14 +356,23 @@ describe('/sample', () => { }).end((err, res) => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); - SampleModel.findById('400000000000000000000001').lean().exec((err, data) => { + SampleModel.findById('400000000000000000000001').lean().exec((err, data: any) => { if (err) return done(err); - should(data).be.null(); + should(data).have.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); + should(data).have.property('_id'); + should(data).have.property('number', '1'); + should(data).have.property('color', 'black'); + should(data).have.property('type', 'granulate'); + should(data).have.property('batch', ''); + should(data.material_id.toString()).be.eql('100000000000000000000004'); + should(data.user_id.toString()).be.eql('000000000000000000000002'); + should(data).have.property('status', -1); + should(data).have.property('note_id', null); done(); }); }); }); - it('deletes the notes of the sample', done => { + it('keeps the notes of the sample', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000002', @@ -342,7 +383,9 @@ describe('/sample', () => { should(res.body).be.eql({status: 'OK'}); NoteModel.findById('500000000000000000000001').lean().exec((err, data) => { if (err) return done(err); - should(data).be.null(); + should(data).have.only.keys('_id', 'comment', 'sample_references', '__v'); + should(data).have.property('comment', 'Stoff gesperrt'); + should(data).have.property('sample_references').with.lengthOf(0); done(); }); }); @@ -367,7 +410,7 @@ describe('/sample', () => { }); }); }); - it('resets references to this sample', done => { + it('keeps references to this sample', done => { TestHelper.request(server, done, { method: 'delete', url: '/sample/400000000000000000000003', @@ -377,10 +420,12 @@ describe('/sample', () => { if (err) return done(err); should(res.body).be.eql({status: 'OK'}); setTimeout(() => { // background action takes some time before we can check - NoteModel.findById('500000000000000000000003').lean().exec((err, data) => { + NoteModel.findById('500000000000000000000003').lean().exec((err, data: any) => { if (err) return done(err); console.log(data); - should(data).have.property('sample_references').with.lengthOf(0); + should(data).have.property('sample_references').with.lengthOf(1); + should(data.sample_references[0].id.toString()).be.eql('400000000000000000000003'); + should(data.sample_references[0]).have.property('relation', 'part to sample'); done(); }); }, 100); @@ -398,7 +443,7 @@ describe('/sample', () => { should(res.body).be.eql({status: 'OK'}); SampleModel.findById('400000000000000000000001').lean().exec((err, data) => { if (err) return done(err); - should(data).be.null(); + should(data).have.property('status', -1); done(); }); }); @@ -486,7 +531,7 @@ describe('/sample', () => { 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.only.keys('_id', 'number', 'color', 'type', 'batch', 'material_id', 'note_id', 'user_id', 'status', '__v'); should(data[0]).have.property('_id'); should(data[0]).have.property('number', 'Rng172'); should(data[0]).have.property('color', 'black'); @@ -494,6 +539,7 @@ describe('/sample', () => { 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('status', 0); should(data[0]).have.property('note_id'); NoteModel.findById(data[0].note_id).lean().exec((err, data: any) => { if (err) return done (err); diff --git a/src/routes/sample.ts b/src/routes/sample.ts index abc5747..a6e9ced 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -16,7 +16,7 @@ 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) => { + SampleModel.find({status: 10}).lean().exec((err, data) => { if (err) return next(err); res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors }) @@ -66,6 +66,11 @@ router.put('/sample/' + IdValidate.parameter(), (req, res, next) => { delete sample.notes; sample.note_id = data._id; } + + // check for changes + if (!_.isEqual(_.pick(IdValidate.stringify(sampleData), _.keys(sample)), _.omit(sample, ['notes']))) { + sample.status = 0; + } SampleModel.findByIdAndUpdate(req.params.id, sample, {new: true}).lean().exec((err, data) => { if (err) return next(err); res.json(SampleValidate.output(data)); @@ -85,22 +90,15 @@ router.delete('/sample/' + IdValidate.parameter(), (req, res, next) => { // only maintain and admin are allowed to edit other user's data if (sampleData.user_id.toString() !== req.authDetails.id && !req.auth(res, ['maintain', 'admin'], 'basic')) return; - SampleModel.findByIdAndDelete(req.params.id).lean().exec(err => { // delete sample + SampleModel.findByIdAndUpdate(req.params.id, {status: -1}).lean().exec(err => { // set sample status if (err) return next(err); if (sampleData.note_id !== null) { - NoteModel.findByIdAndDelete(sampleData.note_id).lean().exec((err, data: any) => { // delete notes + NoteModel.findById(sampleData.note_id).lean().exec((err, data: any) => { // find notes to update note_fields if (err) return next(err); - console.log(data); if (data.hasOwnProperty('custom_fields')) { // update note_fields customFieldsChange(Object.keys(data.custom_fields), -1); } res.json({status: 'OK'}); - NoteModel.updateMany({'sample_references.id': req.params.id}, {$unset: {'sample_references.$': null}}).lean().exec(err => { // remove sample_references - if (err) console.error(err); - NoteModel.collection.updateMany({sample_references: null}, {$pull: {sample_references: null}}, err => { // only works with native MongoDB driver somehow - if (err) console.error(err); - }); - }); }); } else { @@ -124,6 +122,7 @@ router.post('/sample/new', async (req, res, next) => { customFieldsChange(Object.keys(sample.notes.custom_fields), 1); } + sample.status = 0; new NoteModel(sample.notes).save((err, data) => { if (err) return next(err); delete sample.notes; diff --git a/src/test/db.json b/src/test/db.json index 621b385..a5a347a 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -7,10 +7,10 @@ "type": "granulate", "color": "black", "batch": "", - "validated": true, "material_id": {"$oid":"100000000000000000000004"}, "note_id": null, "user_id": {"$oid":"000000000000000000000002"}, + "status": 10, "__v": 0 }, { @@ -19,10 +19,10 @@ "type": "granulate", "color": "natural", "batch": "1560237365", - "validated": true, "material_id": {"$oid":"100000000000000000000001"}, "note_id": {"$oid":"500000000000000000000001"}, "user_id": {"$oid":"000000000000000000000002"}, + "status": 10, "__v": 0 }, { @@ -31,10 +31,10 @@ "type": "part", "color": "black", "batch": "1704-005", - "validated": false, "material_id": {"$oid":"100000000000000000000005"}, "note_id": {"$oid":"500000000000000000000002"}, "user_id": {"$oid":"000000000000000000000003"}, + "status": 0, "__v": 0 }, { @@ -43,10 +43,22 @@ "type": "granulate", "color": "black", "batch": "1653000308", - "validated": false, "material_id": {"$oid":"100000000000000000000005"}, "note_id": {"$oid":"500000000000000000000003"}, "user_id": {"$oid":"000000000000000000000003"}, + "status": 0, + "__v": 0 + }, + { + "_id": {"$oid":"400000000000000000000005"}, + "number": "33", + "type": "granulate", + "color": "black", + "batch": "1653000308", + "material_id": {"$oid":"100000000000000000000005"}, + "note_id": {"$oid":"500000000000000000000003"}, + "user_id": {"$oid":"000000000000000000000003"}, + "status": -1, "__v": 0 } ],