node-rigorous
Version:
Rigorous Framework
118 lines (81 loc) • 4.25 kB
JavaScript
const mongoose = require('mongoose');
const RigorousError = require('../../../facades/RigorousError');
const errorsMessages = require('../../../etc/errorsMessages');
const patch = require('./patch');
const helper_format_checker = require('../../../helpers/format_checker');
const rigorousConfig = require('../../../config');
/**
* Smart Efficient Pagination: https://arpitbhayani.me/techie/fast-and-efficient-pagination-in-mongodb.html
* (Skip is to CPU intensive: https://stackoverflow.com/questions/5539955/how-to-paginate-with-mongoose-in-node-js/23640287#23640287)
*/
function checkPaginationFilter(objectFilter) {
if (!helper_format_checker.isNil(objectFilter._id)) { throw new RigorousError('This field is not needed in multiple result (do findOne) ! So do not use it because it is used in query of pagination'); }
return objectFilter;
}
async function findResults(modelName, query, maxNbResult, reverse, params) {
const { selectAttributesReturned } = params;
const { virtualReturnedFields } = params;
try {
const op = mongoose.model(modelName).find(query)
.select(selectAttributesReturned)
.limit(maxNbResult)
.sort({ updated_at: (reverse ? -1 : 1) });
if (virtualReturnedFields) {
virtualReturnedFields.forEach((virtualFieldReturned) => { op.populate(virtualFieldReturned); });
}
const results = await op.exec();
const resultJson = patch.leanWithId(results);
return resultJson;
} catch (err) {
throw err;
}
}
function buildPaginateQuery(objectTargeted, lastPaginateId, queryParam, reverse) {
const query = {};
Object.keys(queryParam).forEach((key) => { query[key] = queryParam[key]; });
let include = '';
if (lastPaginateId === rigorousConfig.PAGINATION_LAST_ID || lastPaginateId === rigorousConfig.PAGINATION_INITIAL_ID) {
include = 'e';
}
query.updated_at = { [reverse ? `$lt${include}` : `$gt${include}`]: objectTargeted.updated_at };
return query;
}
async function buildObjectTargeted(modelName, lastPaginateId, queryParam, reverse) {
let objectTargeted = null;
if (lastPaginateId === rigorousConfig.PAGINATION_LAST_ID || lastPaginateId === rigorousConfig.PAGINATION_INITIAL_ID) {
objectTargeted = await mongoose.model(modelName).findOne({
...queryParam,
updated_at: { [reverse ? '$lte' : '$gte']: reverse ? new Date(9900, 1, 1).getTime() : new Date(1, 1, 1).getTime() }, // handle lastPaginateId = initial or final
})
.sort({ updated_at: (reverse ? -1 : 1) })
.exec();
} else {
objectTargeted = await mongoose.model(modelName).findOne({
...queryParam,
_id: lastPaginateId,
})
.exec();
}
return objectTargeted;
}
/**
* params.virtualReturnedFields = [{ path: 'owner', select: { xx: 1, yy: 1 } }]
*/
module.exports = async (modelName, queryParam, maxNbResult, lastPaginateId, reverse, params) => {
if (helper_format_checker.isNil(params)) { throw new RigorousError(errorsMessages.OperationError); }
if (helper_format_checker.isNil(params.selectAttributesReturned) && helper_format_checker.isObjectEmpty(params.selectAttributesReturned)) { throw new RigorousError(errorsMessages.OperationError); }
checkPaginationFilter(queryParam);
try {
// We do not skip (=offset) for scalability reason: https://stackoverflow.com/questions/5539955/how-to-paginate-with-mongoose-in-node-js
const objectTargeted = await buildObjectTargeted(modelName, lastPaginateId, queryParam, reverse);
if (!objectTargeted) {
// no data found (not error but it means initial query is empty, for exemple query all swits of lists but this list is empty)
return [];
}
const query = buildPaginateQuery(objectTargeted, lastPaginateId, queryParam, reverse);
const results = await findResults(modelName, query, maxNbResult, reverse, params);
return results;
} catch (errResult) {
throw new RigorousError(errorsMessages.OperationError, errResult);
}
};