first implementation of fields
This commit is contained in:
parent
e5cc661928
commit
52eb828bea
@ -42,9 +42,15 @@
|
|||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
- name: fields
|
||||||
|
description: the fields to include in the output as array, defaults to ['_id','number','type','batch','material_id','color','condition','note_id','user_id','added']
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: '&fields[]=number&fields[]=batch'
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: samples overview
|
description: samples overview (if the csv parameter is set, this is in CSV instead of JSON format)
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
@ -3,7 +3,7 @@ import cfenv from 'cfenv';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import ChangelogModel from './models/changelog';
|
import ChangelogModel from './models/changelog';
|
||||||
|
|
||||||
// mongoose.set('debug', true); // enable mongoose debug
|
mongoose.set('debug', true); // enable mongoose debug
|
||||||
|
|
||||||
// database urls, prod db url is retrieved automatically
|
// database urls, prod db url is retrieved automatically
|
||||||
const TESTING_URL = 'mongodb://localhost/dfopdb_test';
|
const TESTING_URL = 'mongodb://localhost/dfopdb_test';
|
||||||
|
@ -48,8 +48,9 @@ app.use(require('./helpers/authorize')); // handle authentication
|
|||||||
|
|
||||||
// redirect /api routes for Angular proxy in development
|
// redirect /api routes for Angular proxy in development
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
app.use('/api/:url([^]+)', (req, ignore) => {
|
app.use('/api/:url([^]+)', (req, res) => {
|
||||||
req.url = '/' + req.params.url;
|
req.url = '/' + req.params.url;
|
||||||
|
app.handle(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +259,33 @@ describe('/sample', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('returns only the fields specified', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'get',
|
||||||
|
url: '/samples?status=all&page-size=1&fields[]=number&fields[]=condition&fields[]=color&fields[]=material.name&fields[]=material.mineral',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200,
|
||||||
|
res: [{number: '1', condition: {material: 'copper', weeks: 3, condition_template: '200000000000000000000001'}, color: 'black', material: {name: 'Schulamid 66 GF 25 H', mineral: 0}}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an invalid fields parameter', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'get',
|
||||||
|
url: '/samples?status=all&page-size=1&fields=number',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
res: {status: 'Invalid body format', details: '"fields" must be an array'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects an unknown field name', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'get',
|
||||||
|
url: '/samples?status=all&page-size=1&fields[]=xx',
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 400,
|
||||||
|
res: {status: 'Invalid body format', details: '"fields[0]" must be one of [_id, color, number, type, batch, added, material.name, material.supplier, material.group, material.mineral, material.glass_fiber, material.carbon_fiber, material.number, condition, material_id, note_id, user_id, material._id, material.numbers]'}
|
||||||
|
});
|
||||||
|
});
|
||||||
it('rejects a negative page size', done => {
|
it('rejects a negative page size', done => {
|
||||||
TestHelper.request(server, done, {
|
TestHelper.request(server, done, {
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -302,7 +329,7 @@ describe('/sample', () => {
|
|||||||
httpStatus: 401
|
httpStatus: 401
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}); // TODO: measurement fields
|
||||||
|
|
||||||
describe('GET /samples/{state}', () => {
|
describe('GET /samples/{state}', () => {
|
||||||
it('returns all new samples', done => {
|
it('returns all new samples', done => {
|
||||||
|
@ -48,19 +48,17 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
filters['to-page'] = 0;
|
filters['to-page'] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.sort[0].indexOf('material.') >= 0) { // need to populate materials, material supplier and group
|
query.push(
|
||||||
query.push(
|
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
||||||
{$lookup: {from: 'materials', localField: 'material_id', foreignField: '_id', as: 'material'}},
|
{$set: {material: { $arrayElemAt: ['$material', 0]}}},
|
||||||
{$set: {material: { $arrayElemAt: ['$material', 0]}}},
|
{$lookup: { from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group' }},
|
||||||
{$lookup: { from: 'material_groups', localField: 'material.group_id', foreignField: '_id', as: 'material.group' }},
|
{$set: {'material.group': { $arrayElemAt: ['$material.group.name', 0]}}},
|
||||||
{$set: {'material.group': { $arrayElemAt: ['$material.group.name', 0]}}},
|
{$lookup: { from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'}},
|
||||||
{$lookup: { from: 'material_suppliers', localField: 'material.supplier_id', foreignField: '_id', as: 'material.supplier'}},
|
{$set: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}},
|
||||||
{$set: {'material.supplier': {$arrayElemAt: ['$material.supplier.name', 0]}}},
|
{$set: {'material.number': { $arrayElemAt: ['$material.numbers.number', {$indexOfArray: ['$material.numbers.color', '$color']}]}
|
||||||
{$set: {'material.number': { $arrayElemAt: ['$material.numbers.number', {$indexOfArray: ['$material.numbers.color', '$color']}]}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
if (filters['from-id']) { // from-id specified
|
if (filters['from-id']) { // from-id specified
|
||||||
const fromSample = await SampleModel.findById(filters['from-id']).lean().exec().catch(err => {next(err);});
|
const fromSample = await SampleModel.findById(filters['from-id']).lean().exec().catch(err => {next(err);});
|
||||||
@ -79,9 +77,9 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
query.push({$sort: {[filters.sort[0]]: filters.sort[1], '_id': filters.sort[1]}}); // set _id as secondary sort
|
query.push({$sort: {[filters.sort[0]]: filters.sort[1], '_id': filters.sort[1]}}); // set _id as secondary sort
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.sort[0].indexOf('material.') >= 0) { // unpopulate materials again
|
// if (filters.sort[0].indexOf('material.') >= 0) { // unpopulate materials again
|
||||||
query.push({$unset: 'material'});
|
// query.push({$unset: 'material'});
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (filters['to-page']) {
|
if (filters['to-page']) {
|
||||||
query.push({$skip: Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size'] + Number(filters['to-page'] < 0)}) // number to skip, if going back pages, one page has to be skipped less but on sample more
|
query.push({$skip: Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size'] + Number(filters['to-page'] < 0)}) // number to skip, if going back pages, one page has to be skipped less but on sample more
|
||||||
@ -90,6 +88,16 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
if (filters['page-size']) {
|
if (filters['page-size']) {
|
||||||
query.push({$limit: filters['page-size']});
|
query.push({$limit: filters['page-size']});
|
||||||
}
|
}
|
||||||
|
console.log(filters.fields);
|
||||||
|
const projection = filters.fields.reduce((s, e) => {s[e] = true; return s; }, {});
|
||||||
|
if (filters.fields.indexOf('added') >= 0) { // add added date
|
||||||
|
projection.added = {$toDate: '$_id'};
|
||||||
|
}
|
||||||
|
if (!(filters.fields.indexOf('_id') >= 0)) { // disable _id explicitly
|
||||||
|
console.log('disable id');
|
||||||
|
projection._id = false;
|
||||||
|
}
|
||||||
|
query.push({$project: projection});
|
||||||
|
|
||||||
SampleModel.aggregate(query).exec((err, data) => {
|
SampleModel.aggregate(query).exec((err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
@ -97,14 +105,14 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
data.reverse();
|
data.reverse();
|
||||||
}
|
}
|
||||||
if (filters.csv) { // output as csv // TODO: csv example in OAS
|
if (filters.csv) { // output as csv // TODO: csv example in OAS
|
||||||
csv(_.compact(data.map(e => SampleValidate.output(e))), ['_id', 'number'], (err, data) => {
|
csv(_.compact(data.map(e => SampleValidate.output(e, 'refs'))), ['_id', 'number'], (err, data) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
res.set('Content-Type', 'text/csv');
|
res.set('Content-Type', 'text/csv');
|
||||||
res.send(data);
|
res.send(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
|
res.json(_.compact(data.map(e => SampleValidate.output(e, 'refs')))); // validate all and filter null values from validation errors
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -51,6 +51,32 @@ export default class SampleValidate {
|
|||||||
.min('1970-01-01T00:00:00.000Z')
|
.min('1970-01-01T00:00:00.000Z')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static sortKeys = [
|
||||||
|
'_id',
|
||||||
|
'color',
|
||||||
|
'number',
|
||||||
|
'type',
|
||||||
|
'batch',
|
||||||
|
'added',
|
||||||
|
'material.name',
|
||||||
|
'material.supplier',
|
||||||
|
'material.group',
|
||||||
|
'material.mineral',
|
||||||
|
'material.glass_fiber',
|
||||||
|
'material.carbon_fiber',
|
||||||
|
'material.number'
|
||||||
|
];
|
||||||
|
|
||||||
|
private static fieldKeys = [
|
||||||
|
...SampleValidate.sortKeys,
|
||||||
|
'condition',
|
||||||
|
'material_id',
|
||||||
|
'note_id',
|
||||||
|
'user_id',
|
||||||
|
'material._id',
|
||||||
|
'material.numbers'
|
||||||
|
];
|
||||||
|
|
||||||
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
static input (data, param) { // validate input, set param to 'new' to make all attributes required
|
||||||
if (param === 'new') {
|
if (param === 'new') {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
@ -88,8 +114,11 @@ export default class SampleValidate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static output (data, param = 'refs') { // validate output and strip unwanted properties, returns null if not valid
|
static output (data, param = 'refs+added') { // validate output and strip unwanted properties, returns null if not valid
|
||||||
data.added = data._id.getTimestamp();
|
if (param === 'refs+added') {
|
||||||
|
param = 'refs';
|
||||||
|
data.added = data._id.getTimestamp();
|
||||||
|
}
|
||||||
data = IdValidate.stringify(data);
|
data = IdValidate.stringify(data);
|
||||||
let joiObject;
|
let joiObject;
|
||||||
if (param === 'refs') {
|
if (param === 'refs') {
|
||||||
@ -101,6 +130,7 @@ export default class SampleValidate {
|
|||||||
batch: this.sample.batch,
|
batch: this.sample.batch,
|
||||||
condition: this.sample.condition,
|
condition: this.sample.condition,
|
||||||
material_id: IdValidate.get(),
|
material_id: IdValidate.get(),
|
||||||
|
material: MaterialValidate.outputV(),
|
||||||
note_id: IdValidate.get().allow(null),
|
note_id: IdValidate.get().allow(null),
|
||||||
user_id: IdValidate.get(),
|
user_id: IdValidate.get(),
|
||||||
added: this.sample.added
|
added: this.sample.added
|
||||||
@ -133,8 +163,9 @@ export default class SampleValidate {
|
|||||||
'from-id': IdValidate.get(),
|
'from-id': IdValidate.get(),
|
||||||
'to-page': Joi.number().integer(),
|
'to-page': Joi.number().integer(),
|
||||||
'page-size': Joi.number().integer().min(1),
|
'page-size': Joi.number().integer().min(1),
|
||||||
sort: Joi.string().pattern(/^(_id|color|number|type|batch|added|material\.name|material\.supplier|material\.group|material\.mineral|material\.glass_fiber|material\.carbon_fiber|material\.number)-(asc|desc)$/m).default('_id-asc'),
|
sort: Joi.string().pattern(new RegExp('^(' + this.sortKeys.join('|').replace(/\./g, '\\.') + ')-(asc|desc)$', 'm')).default('_id-asc'),
|
||||||
csv: Joi.boolean().default(false)
|
csv: Joi.boolean().default(false),
|
||||||
|
fields: Joi.array().items(Joi.string().valid(...this.fieldKeys)).default(['_id','number','type','batch','material_id','color','condition','note_id','user_id','added'])
|
||||||
}).with('to-page', 'page-size').validate(data);
|
}).with('to-page', 'page-size').validate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user