Files
kami-parse-server/src/Routers/AggregateRouter.js

168 lines
3.9 KiB
JavaScript
Raw Normal View History

import ClassesRouter from './ClassesRouter';
import rest from '../rest';
import * as middleware from '../middlewares';
import Parse from 'parse/node';
import UsersRouter from './UsersRouter';
const BASE_KEYS = ['where', 'distinct', 'pipeline', 'hint', 'explain'];
const PIPELINE_KEYS = [
'addFields',
'bucket',
'bucketAuto',
'collStats',
'count',
'currentOp',
'facet',
'geoNear',
'graphLookup',
'group',
'indexStats',
'limit',
'listLocalSessions',
'listSessions',
'lookup',
'match',
'out',
'project',
'redact',
'replaceRoot',
'sample',
'skip',
'sort',
'sortByCount',
'unwind',
];
const ALLOWED_KEYS = [...BASE_KEYS, ...PIPELINE_KEYS];
export class AggregateRouter extends ClassesRouter {
handleFind(req) {
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = {};
if (body.distinct) {
options.distinct = String(body.distinct);
}
if (body.hint) {
options.hint = body.hint;
delete body.hint;
}
if (body.explain) {
options.explain = body.explain;
delete body.explain;
}
if (body.readPreference) {
options.readPreference = body.readPreference;
delete body.readPreference;
}
options.pipeline = AggregateRouter.getPipeline(body);
if (typeof body.where === 'string') {
body.where = JSON.parse(body.where);
}
return rest
.find(
req.config,
req.auth,
this.className(req),
body.where,
options,
req.info.clientSDK,
2020-07-13 13:06:52 -05:00
req.info.context
)
2020-07-13 13:06:52 -05:00
.then((response) => {
for (const result of response.results) {
if (typeof result === 'object') {
UsersRouter.removeHiddenProperties(result);
}
}
return { response };
});
}
/* Builds a pipeline from the body. Originally the body could be passed as a single object,
* and now we support many options
*
* Array
*
* body: [{
* group: { objectId: '$name' },
* }]
*
* Object
*
* body: {
* group: { objectId: '$name' },
* }
*
*
* Pipeline Operator with an Array or an Object
*
* body: {
* pipeline: {
* group: { objectId: '$name' },
* }
* }
*
*/
static getPipeline(body) {
let pipeline = body.pipeline || body;
if (!Array.isArray(pipeline)) {
2020-07-13 13:06:52 -05:00
pipeline = Object.keys(pipeline).map((key) => {
return { [key]: pipeline[key] };
});
}
2020-07-13 13:06:52 -05:00
return pipeline.map((stage) => {
const keys = Object.keys(stage);
if (keys.length != 1) {
throw new Error(
`Pipeline stages should only have one key found ${keys.join(', ')}`
);
}
return AggregateRouter.transformStage(keys[0], stage);
});
}
static transformStage(stageName, stage) {
if (ALLOWED_KEYS.indexOf(stageName) === -1) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Invalid parameter for query: ${stageName}`
);
}
if (stageName === 'group') {
if (Object.prototype.hasOwnProperty.call(stage[stageName], '_id')) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Invalid parameter for query: group. Please use objectId instead of _id`
);
}
if (!Object.prototype.hasOwnProperty.call(stage[stageName], 'objectId')) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Invalid parameter for query: group. objectId is required`
);
}
stage[stageName]._id = stage[stageName].objectId;
delete stage[stageName].objectId;
}
return { [`$${stageName}`]: stage[stageName] };
}
mountRoutes() {
this.route(
'GET',
'/aggregate/:className',
middleware.promiseEnforceMasterKeyAccess,
2020-07-13 13:06:52 -05:00
(req) => {
return this.handleFind(req);
}
);
}
}
export default AggregateRouter;