diff --git a/api/material.yaml b/api/material.yaml index 967071c..3122e32 100644 --- a/api/material.yaml +++ b/api/material.yaml @@ -19,9 +19,9 @@ 500: $ref: 'api.yaml#/components/responses/500' -/materials/{group}: +/materials/{state}: parameters: - - $ref: 'api.yaml#/components/parameters/Group' + - $ref: 'api.yaml#/components/parameters/State' get: summary: lists all new/deleted materials description: 'Auth: basic, levels: maintain, admin' @@ -168,5 +168,51 @@ $ref: 'api.yaml#/components/responses/401' 403: $ref: 'api.yaml#/components/responses/403' + 500: + $ref: 'api.yaml#/components/responses/500' + +/material/groups: + get: + summary: list all existing material groups + description: 'Auth: all, levels: read, write, maintain, dev, admin' + tags: + - /material + responses: + 200: + description: all material groups + content: + application/json: + schema: + type: array + items: + type: string + example: PA66 + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' + 500: + $ref: 'api.yaml#/components/responses/500' + +/material/suppliers: + get: + summary: list all existing material suppliers + description: 'Auth: all, levels: read, write, maintain, dev, admin' + tags: + - /material + responses: + 200: + description: all material suppliers + content: + application/json: + schema: + type: array + items: + type: string + example: BASF + 401: + $ref: 'api.yaml#/components/responses/401' + 403: + $ref: 'api.yaml#/components/responses/403' 500: $ref: 'api.yaml#/components/responses/500' \ No newline at end of file diff --git a/api/parameters.yaml b/api/parameters.yaml index b4586f7..3cbe49b 100644 --- a/api/parameters.yaml +++ b/api/parameters.yaml @@ -14,7 +14,7 @@ Name: schema: type: string -Group: +State: name: group description: 'possible values: new, deleted' in: path diff --git a/api/sample.yaml b/api/sample.yaml index 67f25ac..00e35ff 100644 --- a/api/sample.yaml +++ b/api/sample.yaml @@ -19,9 +19,9 @@ 500: $ref: 'api.yaml#/components/responses/500' -/samples/{group}: +/samples/{state}: parameters: - - $ref: 'api.yaml#/components/parameters/Group' + - $ref: 'api.yaml#/components/parameters/State' get: summary: all new/deleted samples in overview description: 'Auth: basic, levels: maintain, admin' diff --git a/src/models/material.ts b/src/models/material.ts index 71d6b34..a183020 100644 --- a/src/models/material.ts +++ b/src/models/material.ts @@ -1,9 +1,11 @@ import mongoose from 'mongoose'; +import MaterialSupplierModel from '../models/material_suppliers'; +import MaterialGroupsModel from '../models/material_groups'; const MaterialSchema = new mongoose.Schema({ name: {type: String, index: {unique: true}}, - supplier: String, - group: String, + supplier_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialSupplierModel}, + group_id: {type: mongoose.Schema.Types.ObjectId, ref: MaterialGroupsModel}, mineral: String, glass_fiber: String, carbon_fiber: String, diff --git a/src/models/material_groups.ts b/src/models/material_groups.ts new file mode 100644 index 0000000..e9c9861 --- /dev/null +++ b/src/models/material_groups.ts @@ -0,0 +1,7 @@ +import mongoose from 'mongoose'; + +const MaterialGroupsSchema = new mongoose.Schema({ + name: {type: String, index: {unique: true}} +}); + +export default mongoose.model('material_groups', MaterialGroupsSchema); \ No newline at end of file diff --git a/src/models/material_suppliers.ts b/src/models/material_suppliers.ts new file mode 100644 index 0000000..573d397 --- /dev/null +++ b/src/models/material_suppliers.ts @@ -0,0 +1,7 @@ +import mongoose from 'mongoose'; + +const MaterialSuppliersSchema = new mongoose.Schema({ + name: {type: String, index: {unique: true}} +}); + +export default mongoose.model('material_suppliers', MaterialSuppliersSchema); \ No newline at end of file diff --git a/src/routes/material.spec.ts b/src/routes/material.spec.ts index 56f094e..31d7137 100644 --- a/src/routes/material.spec.ts +++ b/src/routes/material.spec.ts @@ -4,7 +4,6 @@ import MaterialModel from '../models/material'; import TestHelper from "../test/helper"; import globals from '../globals'; -// TODO: color name must be unique to get color number // TODO: separate supplier/ material name into own collections describe('/material', () => { @@ -80,7 +79,7 @@ describe('/material', () => { }); }); - describe('GET /materials/{group}', () => { + describe('GET /materials/{state}', () => { it('returns all new materials', done => { TestHelper.request(server, done, { method: 'get', @@ -767,4 +766,92 @@ describe('/material', () => { }); }); }); + + describe('GET /material/groups', () => { + it('returns all groups', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/groups', + 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.material_groups.length); + should(res.body[0]).be.eql(json.collections.material_groups[0].name); + should(res.body).matchEach(group => { + should(group).be.type('string'); + }); + done(); + }); + }); + it('works with an API key', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/groups', + 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.material_groups.length); + should(res.body[0]).be.eql(json.collections.material_groups[0].name); + should(res.body).matchEach(group => { + should(group).be.type('string'); + }); + done(); + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/groups', + httpStatus: 401 + }); + }); + }); + + describe('GET /material/suppliers', () => { + it('returns all suppliers', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/suppliers', + 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.material_suppliers.length); + should(res.body[0]).be.eql(json.collections.material_suppliers[0].name); + should(res.body).matchEach(supplier => { + should(supplier).be.type('string'); + }); + done(); + }); + }); + it('works with an API key', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/suppliers', + 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.material_suppliers.length); + should(res.body[0]).be.eql(json.collections.material_suppliers[0].name); + should(res.body).matchEach(supplier => { + should(supplier).be.type('string'); + }); + done(); + }); + }); + it('rejects unauthorized requests', done => { + TestHelper.request(server, done, { + method: 'get', + url: '/material/suppliers', + httpStatus: 401 + }); + }); + }); }); \ No newline at end of file diff --git a/src/routes/material.ts b/src/routes/material.ts index 1711eb5..efdd38b 100644 --- a/src/routes/material.ts +++ b/src/routes/material.ts @@ -4,6 +4,8 @@ import _ from 'lodash'; import MaterialValidate from './validate/material'; import MaterialModel from '../models/material' import SampleModel from '../models/sample'; +import MaterialGroupsModel from '../models/material_groups'; +import MaterialSuppliersModel from '../models/material_suppliers'; import IdValidate from './validate/id'; import res400 from './validate/res400'; import mongoose from 'mongoose'; @@ -16,17 +18,26 @@ const router = express.Router(); router.get('/materials', (req, res, next) => { if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; - MaterialModel.find({status:globals.status.validated}).lean().exec((err, data) => { + MaterialModel.find({status:globals.status.validated}).populate('group_id').populate('supplier_id').lean().exec((err, data) => { if (err) return next(err); + console.log(data); + data.forEach((material: any) => { // map group and supplier + material.group = material.group_id.name; + material.supplier = material.supplier_id.name; + }); res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors }); }); -router.get('/materials/:group(new|deleted)', (req, res, next) => { +router.get('/materials/:state(new|deleted)', (req, res, next) => { if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; - MaterialModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => { + MaterialModel.find({status: globals.status[req.params.state]}).populate('group_id').populate('supplier_id').lean().exec((err, data) => { if (err) return next(err); + data.forEach((material: any) => { // map group and supplier + material.group = material.group_id.name; + material.supplier = material.supplier_id.name; + }); res.json(_.compact(data.map(e => MaterialValidate.output(e)))); // validate all and filter null values from validation errors }); }); @@ -34,12 +45,15 @@ router.get('/materials/:group(new|deleted)', (req, res, next) => { 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: any) => { + MaterialModel.findById(req.params.id).populate('group_id').populate('supplier_id').lean().exec((err, data: any) => { if (err) return next(err); if (!data) { return res.status(404).json({status: 'Not found'}); } + + data.group = data.group_id.name; + data.supplier = data.supplier_id.name; if (data.status === globals.status.deleted && !req.auth(res, ['maintain', 'admin'], 'all')) return; // deleted materials only available for maintain/admin res.json(MaterialValidate.output(data)); }); @@ -108,7 +122,7 @@ router.put('/material/restore/' + IdValidate.parameter(), (req, res, next) => { }); }); -router.post('/material/new', async (req, res, next) => { +router.post('/material/new', async (req, res, next) => { // TODO: check supplier and group, also for PUT and DELETE if (!req.auth(res, ['write', 'maintain', 'dev', 'admin'], 'basic')) return; const {error, value: material} = MaterialValidate.input(req.body, 'new'); @@ -123,6 +137,26 @@ router.post('/material/new', async (req, res, next) => { }); }); +router.get('/material/groups', (req, res, next) => { + if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; + + MaterialGroupsModel.find().lean().exec((err, data: any) => { + if (err) return next(err); + + res.json(_.compact(data.map(e => MaterialValidate.outputGroups(e.name)))); // validate all and filter null values from validation errors + }); +}); + +router.get('/material/suppliers', (req, res, next) => { + if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return; + + MaterialSuppliersModel.find().lean().exec((err, data: any) => { + if (err) return next(err); + + res.json(_.compact(data.map(e => MaterialValidate.outputSuppliers(e.name)))); // validate all and filter null values from validation errors + }); +}); + module.exports = router; diff --git a/src/routes/measurement.spec.ts b/src/routes/measurement.spec.ts index c27bf63..af21400 100644 --- a/src/routes/measurement.spec.ts +++ b/src/routes/measurement.spec.ts @@ -3,6 +3,7 @@ import MeasurementModel from '../models/measurement'; import TestHelper from "../test/helper"; import globals from '../globals'; +// TODO: test unique material names and produced error code describe('/measurement', () => { let server; diff --git a/src/routes/sample.spec.ts b/src/routes/sample.spec.ts index b90a722..11a5641 100644 --- a/src/routes/sample.spec.ts +++ b/src/routes/sample.spec.ts @@ -84,7 +84,7 @@ describe('/sample', () => { }); }); - describe('GET /samples/{group}', () => { + describe('GET /samples/{state}', () => { it('returns all new samples', done => { TestHelper.request(server, done, { method: 'get', diff --git a/src/routes/sample.ts b/src/routes/sample.ts index e8ed1f7..0155b8c 100644 --- a/src/routes/sample.ts +++ b/src/routes/sample.ts @@ -27,10 +27,10 @@ router.get('/samples', (req, res, next) => { }) }); -router.get('/samples/:group(new|deleted)', (req, res, next) => { +router.get('/samples/:state(new|deleted)', (req, res, next) => { if (!req.auth(res, ['maintain', 'admin'], 'basic')) return; - SampleModel.find({status: globals.status[req.params.group]}).lean().exec((err, data) => { + SampleModel.find({status: globals.status[req.params.state]}).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 }); diff --git a/src/routes/validate/material.ts b/src/routes/validate/material.ts index 805ccd2..225391a 100644 --- a/src/routes/validate/material.ts +++ b/src/routes/validate/material.ts @@ -83,6 +83,16 @@ export default class MaterialValidate { // validate input for material return error !== undefined? null : value; } + static outputGroups (data) {// validate groups output and strip unwanted properties, returns null if not valid + const {value, error} = this.material.group.validate(data, {stripUnknown: true}); + return error !== undefined? null : value; + } + + static outputSuppliers (data) {// validate suppliers output and strip unwanted properties, returns null if not valid + const {value, error} = this.material.supplier.validate(data, {stripUnknown: true}); + return error !== undefined? null : value; + } + static outputV() { // return output validator return Joi.object({ _id: IdValidate.get(), diff --git a/src/test/db.json b/src/test/db.json index de4070f..b65c0ec 100644 --- a/src/test/db.json +++ b/src/test/db.json @@ -149,8 +149,8 @@ { "_id": {"$oid":"100000000000000000000001"}, "name": "Stanyl TW 200 F8", - "supplier": "DSM", - "group": "PA46", + "supplier_id": {"$oid":"110000000000000000000001"}, + "group_id": {"$oid":"900000000000000000000001"}, "mineral": 0, "glass_fiber": 40, "carbon_fiber": 0, @@ -170,8 +170,8 @@ { "_id": {"$oid":"100000000000000000000002"}, "name": "Ultramid T KR 4355 G7", - "supplier": "BASF", - "group": "PA6/6T", + "supplier_id": {"$oid":"110000000000000000000002"}, + "group_id": {"$oid":"900000000000000000000002"}, "mineral": 0, "glass_fiber": 35, "carbon_fiber": 0, @@ -191,8 +191,8 @@ { "_id": {"$oid":"100000000000000000000003"}, "name": "PA GF 50 black (2706)", - "supplier": "Akro-Plastic", - "group": "PA66+PA6I/6T", + "supplier_id": {"$oid":"110000000000000000000003"}, + "group_id": {"$oid":"900000000000000000000003"}, "mineral": 0, "glass_fiber": 0, "carbon_fiber": 0, @@ -204,8 +204,8 @@ { "_id": {"$oid":"100000000000000000000004"}, "name": "Schulamid 66 GF 25 H", - "supplier": "Schulmann", - "group": "PA66", + "supplier_id": {"$oid":"110000000000000000000004"}, + "group_id": {"$oid":"900000000000000000000004"}, "mineral": 0, "glass_fiber": 25, "carbon_fiber": 0, @@ -221,8 +221,8 @@ { "_id": {"$oid":"100000000000000000000005"}, "name": "Amodel A 1133 HS", - "supplier": "Solvay", - "group": "PPA", + "supplier_id": {"$oid":"110000000000000000000005"}, + "group_id": {"$oid":"900000000000000000000005"}, "mineral": 0, "glass_fiber": 33, "carbon_fiber": 0, @@ -238,8 +238,8 @@ { "_id": {"$oid":"100000000000000000000006"}, "name": "PK-HM natural (4773)", - "supplier": "Akro-Plastic", - "group": "PK", + "supplier_id": {"$oid":"110000000000000000000003"}, + "group_id": {"$oid":"900000000000000000000006"}, "mineral": 0, "glass_fiber": 0, "carbon_fiber": 0, @@ -255,8 +255,8 @@ { "_id": {"$oid":"100000000000000000000007"}, "name": "Ultramid A4H", - "supplier": "BASF", - "group": "PA66", + "supplier_id": {"$oid":"110000000000000000000002"}, + "group_id": {"$oid":"900000000000000000000004"}, "mineral": 0, "glass_fiber": 0, "carbon_fiber": 0, @@ -272,8 +272,8 @@ { "_id": {"$oid":"100000000000000000000008"}, "name": "Latamid 66 H 2 G 30", - "supplier": "LATI", - "group": "PA66", + "supplier_id": {"$oid":"110000000000000000000006"}, + "group_id": {"$oid":"900000000000000000000004"}, "mineral": 0, "glass_fiber": 30, "carbon_fiber": 0, @@ -287,6 +287,70 @@ "__v": 0 } ], + "material_groups": [ + { + "_id": {"$oid":"900000000000000000000001"}, + "name": "PA46", + "__v": 0 + }, + { + "_id": {"$oid":"900000000000000000000002"}, + "name": "PA6/6T", + "__v": 0 + }, + { + "_id": {"$oid":"900000000000000000000003"}, + "name": "PA66+PA6I/6T", + "__v": 0 + }, + { + "_id": {"$oid":"900000000000000000000004"}, + "name": "PA66", + "__v": 0 + }, + { + "_id": {"$oid":"900000000000000000000005"}, + "name": "PPA", + "__v": 0 + }, + { + "_id": {"$oid":"900000000000000000000006"}, + "name": "PK", + "__v": 0 + } + ], + "material_suppliers": [ + { + "_id": {"$oid":"110000000000000000000001"}, + "name": "DSM", + "__v": 0 + }, + { + "_id": {"$oid":"110000000000000000000002"}, + "name": "BASF", + "__v": 0 + }, + { + "_id": {"$oid":"110000000000000000000003"}, + "name": "Akro-Plastic", + "__v": 0 + }, + { + "_id": {"$oid":"110000000000000000000004"}, + "name": "Schulmann", + "__v": 0 + }, + { + "_id": {"$oid":"110000000000000000000005"}, + "name": "Solvay", + "__v": 0 + }, + { + "_id": {"$oid":"110000000000000000000006"}, + "name": "LATI", + "__v": 0 + } + ], "measurements": [ { "_id": {"$oid":"800000000000000000000001"},