sorting for direct sample properties added
This commit is contained in:
		@@ -30,6 +30,12 @@
 | 
				
			|||||||
        schema:
 | 
					        schema:
 | 
				
			||||||
          type: string
 | 
					          type: string
 | 
				
			||||||
        example: 30
 | 
					        example: 30
 | 
				
			||||||
 | 
					      - name: sort
 | 
				
			||||||
 | 
					        description: sorting of results, in format 'key-asc/desc'
 | 
				
			||||||
 | 
					        in: query
 | 
				
			||||||
 | 
					        schema:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					        example: color-asc
 | 
				
			||||||
    responses:
 | 
					    responses:
 | 
				
			||||||
      200:
 | 
					      200:
 | 
				
			||||||
        description: samples overview
 | 
					        description: samples overview
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -177,6 +177,59 @@ describe('/sample', () => {
 | 
				
			|||||||
        done();
 | 
					        done();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    it('sorts the samples ascending', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples?status=all&sort=color-asc',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body[0]).have.property('color', 'black');
 | 
				
			||||||
 | 
					        should(res.body[res.body.length - 1]).have.property('color', 'natural');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('sorts the samples descending', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples?status=all&sort=number-desc',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body[0]).have.property('number', 'Rng36');
 | 
				
			||||||
 | 
					        should(res.body[1]).have.property('number', '33');
 | 
				
			||||||
 | 
					        should(res.body[res.body.length - 1]).have.property('number', '1');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('sorts the samples correctly in combination with paging', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples?status=all&sort=color-asc&page-size=2&from-id=400000000000000000000006',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body[0]).have.property('_id', '400000000000000000000006');
 | 
				
			||||||
 | 
					        should(res.body[1]).have.property('_id', '400000000000000000000002');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('sorts the samples correctly in combination with going pages backward', done => {
 | 
				
			||||||
 | 
					      TestHelper.request(server, done, {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: '/samples?status=all&sort=color-desc&page-size=2&from-id=400000000000000000000004&to-page=-1',
 | 
				
			||||||
 | 
					        auth: {basic: 'janedoe'},
 | 
				
			||||||
 | 
					        httpStatus: 200
 | 
				
			||||||
 | 
					      }).end((err, res) => {
 | 
				
			||||||
 | 
					        if (err) return done(err);
 | 
				
			||||||
 | 
					        should(res.body[0]).have.property('_id', '400000000000000000000002');
 | 
				
			||||||
 | 
					        should(res.body[1]).have.property('_id', '400000000000000000000006');
 | 
				
			||||||
 | 
					        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',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ import db from '../db';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/samples', (req, res, next) => {
 | 
					router.get('/samples', async (req, res, next) => {
 | 
				
			||||||
  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
					  if (!req.auth(res, ['read', 'write', 'maintain', 'dev', 'admin'], 'all')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {error, value: filters} = SampleValidate.query(req.query);
 | 
					  const {error, value: filters} = SampleValidate.query(req.query);
 | 
				
			||||||
@@ -37,29 +37,55 @@ router.get('/samples', (req, res, next) => {
 | 
				
			|||||||
  else {  // default
 | 
					  else {  // default
 | 
				
			||||||
    status = {status: globals.status.validated};
 | 
					    status = {status: globals.status.validated};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  const query = SampleModel.find(status);
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sort = [];
 | 
				
			||||||
 | 
					  let paging = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // sorting
 | 
				
			||||||
 | 
					  filters.sort = filters.sort.split('-');
 | 
				
			||||||
 | 
					  filters.sort[0] = filters.sort[0] === 'added' ? '_id' : filters.sort[0];  // route added sorting criteria to _id
 | 
				
			||||||
 | 
					  filters.sort[1] = filters.sort[1] === 'desc' ? -1 : 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!filters['to-page']) {  // set to-page default
 | 
				
			||||||
 | 
					    filters['to-page'] = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (filters['from-id']) {  // from-id specified
 | 
				
			||||||
 | 
					    const fromSample = SampleValidate.output(await SampleModel.findById(filters['from-id']).lean().exec().catch(err => {next(err);}));
 | 
				
			||||||
 | 
					    if (fromSample instanceof Error) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((filters['to-page'] === 0 && filters.sort[1] === 1) || (filters.sort[1] * filters['to-page'] > 0)) {  // asc
 | 
				
			||||||
 | 
					      paging = {$or: [{[filters.sort[0]]: {$gt: fromSample[filters.sort[0]]}}, {$and: [{[filters.sort[0]]: fromSample[filters.sort[0]]}, {_id: {$gte: mongoose.Types.ObjectId(filters['from-id'])}}]}]};
 | 
				
			||||||
 | 
					      sort.push([filters.sort[0], 1]);
 | 
				
			||||||
 | 
					      sort.push(['_id', 1]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					      paging = {$or: [{[filters.sort[0]]: {$lt: fromSample[filters.sort[0]]}}, {$and: [{[filters.sort[0]]: fromSample[filters.sort[0]]}, {_id: {$lte: mongoose.Types.ObjectId(filters['from-id'])}}]}]};
 | 
				
			||||||
 | 
					      sort.push([filters.sort[0], -1]);
 | 
				
			||||||
 | 
					      sort.push(['_id', -1]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {  // sort from beginning
 | 
				
			||||||
 | 
					    sort.push([filters.sort[0], filters.sort[1]]);  // set _id as secondary sort
 | 
				
			||||||
 | 
					    sort.push(['_id', filters.sort[1]]);  // set _id as secondary sort
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const query = SampleModel.find({$and: [status, paging]});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (filters['page-size']) {
 | 
					  if (filters['page-size']) {
 | 
				
			||||||
    query.limit(filters['page-size']);
 | 
					    query.limit(filters['page-size']);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (filters['from-id']) {
 | 
					  if (filters['to-page']) {
 | 
				
			||||||
    if (filters['to-page'] && filters['to-page'] < 0) {
 | 
					    query.skip(Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size'] + Number(filters['to-page'] < 0));
 | 
				
			||||||
      query.lt('_id', mongoose.Types.ObjectId(filters['from-id']));  // TODO: consider sorting
 | 
					 | 
				
			||||||
      query.sort({_id: -1});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      query.gte('_id', mongoose.Types.ObjectId(filters['from-id']));  // TODO: consider sorting
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (filters['to-page']) {
 | 
					  query.sort(sort);
 | 
				
			||||||
    query.skip(Math.abs(filters['to-page'] + Number(filters['to-page'] < 0)) * filters['page-size']);  // TODO: check order for negative numbers
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  query.lean().exec((err, data) => {
 | 
					  query.lean().exec((err, data) => {
 | 
				
			||||||
    if (err) return next(err);
 | 
					    if (err) return next(err);
 | 
				
			||||||
    if (filters['to-page'] && 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
 | 
					    res.json(_.compact(data.map(e => SampleValidate.output(e))));  // validate all and filter null values from validation errors
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,8 @@ export default class SampleValidate {
 | 
				
			|||||||
      status: Joi.string().valid('validated', 'new', 'all'),
 | 
					      status: Joi.string().valid('validated', 'new', 'all'),
 | 
				
			||||||
      '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)-(asc|desc)$/m).default('_id-asc')  // TODO: material keys
 | 
				
			||||||
    }).with('to-page', 'page-size').validate(data);
 | 
					    }).with('to-page', 'page-size').validate(data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user