base for csv export
This commit is contained in:
		@@ -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,8 +89,10 @@ 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});
 | 
				
			||||||
 | 
					          if (!/^\/api/m.test(req.url)){
 | 
				
			||||||
            delete req.query.key;  // delete query parameter to avoid interference with later validation
 | 
					            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();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    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
 | 
					      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