base for csv export
This commit is contained in:
parent
8aa051f0bd
commit
e5cc661928
@ -36,6 +36,12 @@
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: color-asc
|
example: color-asc
|
||||||
|
- name: csv
|
||||||
|
description: output as csv
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: samples overview
|
description: samples overview
|
||||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -2203,6 +2203,23 @@
|
|||||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.5.tgz",
|
||||||
"integrity": "sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ=="
|
"integrity": "sha512-gWJOWYFrhQ8j7pVm0EM8Slr+EPVq1Phf6lvzvD/WCeqkrx/f2xBI0xOsRRS9xCn3I4vKtP519dvs3TP09r24wQ=="
|
||||||
},
|
},
|
||||||
|
"json2csv": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-QFMifUX1y8W2tKi2TwZpnzf2rHdZvzdmgZUMEMDF46F90f4a9mUeWfx/qg4kzXSZYJYc3cWA5O+eLXk5lj9g8g==",
|
||||||
|
"requires": {
|
||||||
|
"commander": "^5.0.0",
|
||||||
|
"jsonparse": "^1.3.1",
|
||||||
|
"lodash.get": "^4.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
|
||||||
@ -2212,6 +2229,11 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsonparse": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
|
||||||
|
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
|
||||||
|
},
|
||||||
"jszip": {
|
"jszip": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"helmet": "^3.22.0",
|
"helmet": "^3.22.0",
|
||||||
"json-schema": "^0.2.5",
|
"json-schema": "^0.2.5",
|
||||||
|
"json2csv": "^5.0.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mongo-sanitize": "^1.1.0",
|
"mongo-sanitize": "^1.1.0",
|
||||||
"mongoose": "^5.8.7",
|
"mongoose": "^5.8.7",
|
||||||
|
@ -89,7 +89,9 @@ function key (req, next): any { // checks API key and returns changed user obje
|
|||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
if (data.length === 1) { // one user found
|
if (data.length === 1) { // one user found
|
||||||
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
|
resolve({level: data[0].level, name: data[0].name, id: data[0]._id.toString(), location: data[0].location});
|
||||||
delete req.query.key; // delete query parameter to avoid interference with later validation
|
if (!/^\/api/m.test(req.url)){
|
||||||
|
delete req.query.key; // delete query parameter to avoid interference with later validation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
|
7
src/helpers/csv.ts
Normal file
7
src/helpers/csv.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import {parseAsync} from 'json2csv';
|
||||||
|
|
||||||
|
export default function csv(input: object, fields: string[], f: (err, data) => void) {
|
||||||
|
parseAsync(input)
|
||||||
|
.then(csv => f(null, csv))
|
||||||
|
.catch(err => f(err, null));
|
||||||
|
}
|
@ -48,9 +48,8 @@ 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, res) => {
|
app.use('/api/:url([^]+)', (req, ignore) => {
|
||||||
req.url = '/' + req.params.url;
|
req.url = '/' + req.params.url;
|
||||||
app.handle(req, res);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +244,21 @@ describe('/sample', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('returns a correct csv file if specified', done => {
|
||||||
|
TestHelper.request(server, done, {
|
||||||
|
method: 'get',
|
||||||
|
url: '/samples?status=all&page-size=2&csv=true',
|
||||||
|
contentType: /text\/csv/,
|
||||||
|
auth: {basic: 'janedoe'},
|
||||||
|
httpStatus: 200
|
||||||
|
}).end((err, res) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
should(res.text).be.eql('"_id","number","type","color","batch","condition","material_id","note_id","user_id","added"\r\n' +
|
||||||
|
'"400000000000000000000001","1","granulate","black","","{""material"":""copper"",""weeks"":3,""condition_template"":""200000000000000000000001""}","100000000000000000000004",,"000000000000000000000002","2004-01-10T13:37:04.000Z"\r\n' +
|
||||||
|
'"400000000000000000000002","21","granulate","natural","1560237365","{""material"":""copper"",""weeks"":3,""condition_template"":""200000000000000000000001""}","100000000000000000000001","500000000000000000000001","000000000000000000000002","2004-01-10T13:37:04.000Z"');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
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',
|
||||||
|
@ -15,6 +15,7 @@ import ConditionTemplateModel from '../models/condition_template';
|
|||||||
import ParametersValidate from './validate/parameters';
|
import ParametersValidate from './validate/parameters';
|
||||||
import globals from '../globals';
|
import globals from '../globals';
|
||||||
import db from '../db';
|
import db from '../db';
|
||||||
|
import csv from '../helpers/csv';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -54,7 +55,10 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
{$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']}]}
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +96,16 @@ router.get('/samples', async (req, res, next) => {
|
|||||||
if (filters['to-page'] < 0) {
|
if (filters['to-page'] < 0) {
|
||||||
data.reverse();
|
data.reverse();
|
||||||
}
|
}
|
||||||
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
|
if (filters.csv) { // output as csv // TODO: csv example in OAS
|
||||||
|
csv(_.compact(data.map(e => SampleValidate.output(e))), ['_id', 'number'], (err, data) => {
|
||||||
|
if (err) return next(err);
|
||||||
|
res.set('Content-Type', 'text/csv');
|
||||||
|
res.send(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.json(_.compact(data.map(e => SampleValidate.output(e)))); // validate all and filter null values from validation errors
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -133,7 +133,8 @@ 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)-(asc|desc)$/m).default('_id-asc') // TODO: material keys
|
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'),
|
||||||
|
csv: Joi.boolean().default(false)
|
||||||
}).with('to-page', 'page-size').validate(data);
|
}).with('to-page', 'page-size').validate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,15 +38,7 @@ export default class TestHelper {
|
|||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
static afterEach (server, done) {
|
static request (server, done, options) { // options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res, default (set to false if you want to dismiss default .end handling)}
|
||||||
server.close(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
static after(done) {
|
|
||||||
db.disconnect(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
static request (server, done, options) { // options in form: {method, url, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res, default (set to false if you want to dismiss default .end handling)}
|
|
||||||
let st = supertest(server);
|
let st = supertest(server);
|
||||||
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
|
if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) { // resolve API key
|
||||||
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
options.url += '?key=' + (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
|
||||||
@ -79,8 +71,12 @@ export default class TestHelper {
|
|||||||
st = st.auth(options.auth.basic.name, options.auth.basic.pass)
|
st = st.auth(options.auth.basic.name, options.auth.basic.pass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
st = st.expect('Content-type', /json/)
|
if (options.hasOwnProperty('contentType')) {
|
||||||
.expect(options.httpStatus);
|
st = st.expect('Content-type', options.contentType).expect(options.httpStatus);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
st = st.expect('Content-type', /json/).expect(options.httpStatus);
|
||||||
|
}
|
||||||
if (options.hasOwnProperty('res')) { // evaluate result
|
if (options.hasOwnProperty('res')) { // evaluate result
|
||||||
return st.end((err, res) => {
|
return st.end((err, res) => {
|
||||||
if (err) return done (err);
|
if (err) return done (err);
|
||||||
@ -128,4 +124,12 @@ export default class TestHelper {
|
|||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static afterEach (server, done) {
|
||||||
|
server.close(done);
|
||||||
|
}
|
||||||
|
|
||||||
|
static after(done) {
|
||||||
|
db.disconnect(done);
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user