diff --git a/api/measurement.yaml b/api/measurement.yaml index 453b5e6..fc6ab03 100644 --- a/api/measurement.yaml +++ b/api/measurement.yaml @@ -77,6 +77,40 @@ 500: $ref: 'api.yaml#/components/responses/500' +/measurement/sample/{id}: + parameters: + - $ref: 'api.yaml#/components/parameters/Id' + get: + summary: all measurements of the given sample + description: 'Auth: basic, levels: dev, admin' + tags: + - /measurement + security: + - BasicAuth: [] + responses: + 200: + description: measurement details + content: + application/json: + schema: + type: array + items: + allOf: + - $ref: 'api.yaml#/components/schemas/Measurement' + properties: + status: + type: string + description: can be deleted/new/validated + example: new + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' + 404: + $ref: 'api.yaml#/components/responses/404' + 500: + $ref: 'api.yaml#/components/responses/500' + /measurement/restore/{id}: parameters: - $ref: 'api.yaml#/components/parameters/Id' diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index 668e83b..8c5e1ea 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -404,6 +404,60 @@ describe('/measurement', () => { }); }); + describe('GET /measurement/sample/{id}', () => { + it('returns the right measurements', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/400000000000000000000003', + auth: {basic: 'admin'}, + httpStatus: 200, + res: [ + {_id: '800000000000000000000003', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003', status: 'new'}, + {_id: '800000000000000000000004', sample_id: '400000000000000000000003', values: {val1: 1}, measurement_template: '300000000000000000000003', status: 'deleted'} + ] + }); + }); + it('rejects an API key', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/400000000000000000000003', + auth: {key: 'admin'}, + httpStatus: 401 + }); + }); + it('rejects a write user', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/400000000000000000000003', + auth: {basic: 'janedoe'}, + httpStatus: 403 + }); + }); + it('rejects an invalid id', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/4000000000h0000000000003', + auth: {basic: 'admin'}, + httpStatus: 404 + }); + }); + it('rejects an unknown id', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/000000000000000000000003', + auth: {basic: 'admin'}, + httpStatus: 404 + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/measurement/sample/400000000000000000000003', + httpStatus: 401 + }); + }); + }); + describe('PUT /measurement/restore/{id}', () => { it('sets the status', done => { TestHelper.request(server, done, { diff --git a/src/routes/measurement.ts b/src/routes/measurement.ts index 48cd9b0..701cf8a 100644 --- a/src/routes/measurement.ts +++ b/src/routes/measurement.ts @@ -10,6 +10,7 @@ import res400 from './validate/res400'; import ParametersValidate from './validate/parameters'; import db from '../db'; import globals from '../globals'; +import mongoose from "mongoose"; const router = express.Router(); @@ -82,6 +83,19 @@ router.delete('/measurement/' + IdValidate.parameter(), (req, res, next) => { }); }); +router.get('/measurement/sample/' + IdValidate.parameter(), (req, res, next) => { + if (!req.auth(res, ['dev', 'admin'], 'basic')) return; + + MeasurementModel.find({sample_id: mongoose.Types.ObjectId(req.params.id)}).lean().exec((err, data: any) => { + if (err) return next(err); + if (!data.length) { + return res.status(404).json({status: 'Not found'}); + } + + res.json(_.compact(data.map(e => MeasurementValidate.output(e, req, true)))); + }); +}); + router.put('/measurement/restore/' + IdValidate.parameter(), (req, res, next) => { if (!req.auth(res, ['dev', 'admin'], 'basic')) return; diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index e10d2c2..a4498d0 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -271,7 +271,7 @@ describe('/sample', () => { it('adds the specified measurements', done => { TestHelper.request(server, done, { method: 'get', - url: '/samples?status[]=new&status[]=validated&fields[]=number&fields[]=measurements.kf', + url: '/samples?status[]=new&status[]=validated&fields[]=number&fields[]=measurements.kf.weight%20%25&fields[]=measurements.kf.standard%20deviation', auth: {basic: 'janedoe'}, httpStatus: 200 }).end((err, res) => { @@ -1525,22 +1525,23 @@ describe('/sample', () => { }); }); }); - it('creates a changelog', done => { + it('restores associated measurements', done => { TestHelper.request(server, done, { method: 'put', url: '/sample/restore/400000000000000000000005', auth: {basic: 'admin'}, httpStatus: 200, - req: {}, - log: { - collection: 'samples', - dataAdd: { - group_id: '900000000000000000000002', - supplier_id: '110000000000000000000002', - status: 'new' - }, - dataIgn: ['group_id', 'supplier_id'] - } + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + MeasurementModel.find({sample_id: mongoose.Types.ObjectId('400000000000000000000005')}).lean().exec((err, data: any) => { + if (err) return done(err); + should(data).matchEach(measurement => { + should(measurement).have.property('status', 'new') + }); + done(); + }); }); }); it('rejects an API key', done => { @@ -1598,22 +1599,23 @@ describe('/sample', () => { }); }); }); - it('creates a changelog', done => { + it('validates associated measurements', done => { TestHelper.request(server, done, { method: 'put', url: '/sample/validate/400000000000000000000003', auth: {basic: 'admin'}, httpStatus: 200, - req: {}, - log: { - collection: 'samples', - dataAdd: { - group_id: '900000000000000000000002', - supplier_id: '110000000000000000000002', - status: 'validated' - }, - dataIgn: ['group_id', 'supplier_id'] - } + req: {} + }).end((err, res) => { + if (err) return done (err); + should(res.body).be.eql({status: 'OK'}); + MeasurementModel.find({sample_id: mongoose.Types.ObjectId('400000000000000000000003')}).lean().exec((err, data: any) => { + if (err) return done(err); + should(data).matchEach(measurement => { + should(measurement).have.property('status', 'validated') + }); + done(); + }); }); }); it('allows validating a sample without condition', done => { diff --git a/src/routes/sample.ts b/src/routes/sample.ts index e7a0b03..a8d9357 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -29,6 +29,7 @@ router.get('/samples', async (req, res, next) => { if (!req.auth(res, ['read', 'write', 'dev', 'admin'], 'all')) return; const {error, value: filters} = SampleValidate.query(req.query, ['dev', 'admin'].indexOf(req.authDetails.level) >= 0); + console.log(error); if (error) return res400(error, res); console.log(filters.filters); @@ -221,7 +222,6 @@ router.get('/samples', async (req, res, next) => { const measurementFilterFields = _.uniq(sortFilterKeys.filter(e => /measurements\./.test(e)) .map(e => e.split('.')[1])); // filter measurement names and remove duplicates from parameters if (sortFilterKeys.find(e => /measurements\./.test(e))) { // add measurement fields - console.log(measurementFilterFields); const measurementTemplates = await MeasurementTemplateModel.find({name: {$in: measurementFilterFields}}) .lean().exec().catch(err => {next(err);}); if (measurementTemplates instanceof Error) return; @@ -233,12 +233,7 @@ router.get('/samples', async (req, res, next) => { pipeline: [{$match: {$expr: {$and: [ {$eq: ['$sample_id', '$$sId']}, {$in: ['$measurement_template', measurementTemplates.map(e => mongoose.Types.ObjectId(e._id))]} - ]}}}, - {$project: _.merge( - filters.fields.filter(e => /measurements\./.test(e)) - .map(e => 'values.' + e.split('.')[2]).reduce((s, e) => {s[e] = true; return s; }, {}), - {measurement_template: true, status: true, sample_id: true} - )} + ]}}} ], as: 'measurements' }}); @@ -367,6 +362,7 @@ router.get('/samples', async (req, res, next) => { projection._id = false; } queryPtr.push({$project: projection}); + console.log(JSON.stringify(query)); // use streaming when including spectrum files if (!fieldsToAdd.find(e => e.indexOf(globals.spectrum.spectrum + '.' + globals.spectrum.dpt) >= 0)) { collection.aggregate(query).allowDiskUse(true).exec((err, data) => { @@ -904,6 +900,11 @@ function setStatus (status, req, res, next) { if (!data) { return res.status(404).json({status: 'Not found'}); } - res.json({status: 'OK'}); + MeasurementModel.updateMany({sample_id: mongoose.Types.ObjectId(req.params.id)}, {status}) + .log(req).lean().exec(err => { + if (err) return next(err); + + res.json({status: 'OK'}); + }); }); } \ No newline at end of file diff --git a/src/routes/validate/measurement.ts b/src/routes/validate/measurement.ts index 60dae61..2c31000 100644 --- a/src/routes/validate/measurement.ts +++ b/src/routes/validate/measurement.ts @@ -36,18 +36,23 @@ export default class MeasurementValidate { } } - static output (data, req) { // validate output and strip unwanted properties, returns null if not valid + // validate output and strip unwanted properties, returns null if not valid + static output (data, req, status = false) { data = IdValidate.stringify(data); // spectral data not allowed for read/write users if (['dev', 'admin'].indexOf(req.authDetails.level) < 0 && data.values[globals.spectrum.dpt]) { delete data.values[globals.spectrum.dpt]; } - const {value, error} = Joi.object({ + const validation: any = { _id: IdValidate.get(), sample_id: IdValidate.get(), values: this.measurement.values, measurement_template: IdValidate.get() - }).validate(data, {stripUnknown: true}); + }; + if (status) { + validation.status = Joi.string().valid(...Object.values(globals.status)); + } + const {value, error} = Joi.object(validation).validate(data, {stripUnknown: true}); return error !== undefined? null : value; } diff --git a/src/routes/validate/sample.ts b/src/routes/validate/sample.ts index 15db60f..6e8e497 100644 --- a/src/routes/validate/sample.ts +++ b/src/routes/validate/sample.ts @@ -192,9 +192,11 @@ export default class SampleValidate { if (filterValidation.error) return filterValidation; try { for (let i in data.filters) { - // data.filters[i] = JSON.parse(decodeURIComponent(data.filters[i])); - data.filters[i] = JSON.parse(decodeURIComponent(data.filters[i])); - console.log(data.filters[i]); + try { + data.filters[i] = decodeURIComponent(data.filters[i]); + } + catch (ignore) {} + data.filters[i] = JSON.parse(data.filters[i]); data.filters[i].values = data.filters[i].values.map(e => { // validate filter values if (e === null) { // null values are always allowed return null; @@ -225,6 +227,7 @@ export default class SampleValidate { validator = Joi.object(this.sample); } const {value, error} = validator.validate({[field]: e}); + console.log(error); if (error) throw error; // reject invalid values return value[field]; }); diff --git a/src/test/db.json b/src/test/db.json index 7727315..75d06fd 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -477,6 +477,21 @@ "status": "validated", "measurement_template": {"$oid":"300000000000000000000001"}, "__v": 0 + }, + { + "_id": {"$oid":"800000000000000000000008"}, + "sample_id": {"$oid":"400000000000000000000005"}, + "values": { + "dpt": [ + [3996.12558,98.00555], + [3995.08519,98.03253], + [3993.04480,98.02657] + ], + "device": "Alpha II" + }, + "status": "deleted", + "measurement_template": {"$oid":"300000000000000000000001"}, + "__v": 0 } ], "condition_templates": [