2018-09-01 13:58:06 -04:00
|
|
|
import Parse from 'parse/node';
|
2024-10-22 21:51:58 +03:00
|
|
|
import * as middleware from '../middlewares';
|
|
|
|
|
import rest from '../rest';
|
|
|
|
|
import ClassesRouter from './ClassesRouter';
|
2018-09-01 13:58:06 -04:00
|
|
|
import UsersRouter from './UsersRouter';
|
2017-11-12 13:00:22 -06:00
|
|
|
|
|
|
|
|
export class AggregateRouter extends ClassesRouter {
|
|
|
|
|
handleFind(req) {
|
2020-10-25 15:06:58 -05:00
|
|
|
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
2017-11-12 13:00:22 -06:00
|
|
|
const options = {};
|
|
|
|
|
if (body.distinct) {
|
|
|
|
|
options.distinct = String(body.distinct);
|
|
|
|
|
}
|
2020-01-14 01:14:43 -07:00
|
|
|
if (body.hint) {
|
|
|
|
|
options.hint = body.hint;
|
|
|
|
|
delete body.hint;
|
|
|
|
|
}
|
|
|
|
|
if (body.explain) {
|
|
|
|
|
options.explain = body.explain;
|
|
|
|
|
delete body.explain;
|
|
|
|
|
}
|
2024-03-03 02:27:57 +01:00
|
|
|
if (body.comment) {
|
|
|
|
|
options.comment = body.comment;
|
|
|
|
|
delete body.comment;
|
|
|
|
|
}
|
2020-04-28 20:41:33 +02:00
|
|
|
if (body.readPreference) {
|
|
|
|
|
options.readPreference = body.readPreference;
|
|
|
|
|
delete body.readPreference;
|
|
|
|
|
}
|
2018-08-12 20:05:08 -05:00
|
|
|
options.pipeline = AggregateRouter.getPipeline(body);
|
2017-11-12 13:00:22 -06:00
|
|
|
if (typeof body.where === 'string') {
|
|
|
|
|
body.where = JSON.parse(body.where);
|
|
|
|
|
}
|
2018-09-01 13:58:06 -04:00
|
|
|
return rest
|
|
|
|
|
.find(
|
|
|
|
|
req.config,
|
|
|
|
|
req.auth,
|
|
|
|
|
this.className(req),
|
|
|
|
|
body.where,
|
|
|
|
|
options,
|
2020-07-11 02:17:27 +05:30
|
|
|
req.info.clientSDK,
|
2020-07-13 13:06:52 -05:00
|
|
|
req.info.context
|
2018-09-01 13:58:06 -04:00
|
|
|
)
|
2020-07-13 17:13:08 -05:00
|
|
|
.then(response => {
|
2018-09-01 13:58:06 -04:00
|
|
|
for (const result of response.results) {
|
|
|
|
|
if (typeof result === 'object') {
|
|
|
|
|
UsersRouter.removeHiddenProperties(result);
|
|
|
|
|
}
|
2017-11-22 23:07:45 -08:00
|
|
|
}
|
2018-09-01 13:58:06 -04:00
|
|
|
return { response };
|
|
|
|
|
});
|
2017-11-12 13:00:22 -06:00
|
|
|
}
|
|
|
|
|
|
2018-08-12 20:05:08 -05:00
|
|
|
/* Builds a pipeline from the body. Originally the body could be passed as a single object,
|
2024-10-22 21:51:58 +03:00
|
|
|
* and now we support many options.
|
2018-08-12 20:05:08 -05:00
|
|
|
*
|
|
|
|
|
* Array
|
|
|
|
|
*
|
|
|
|
|
* body: [{
|
|
|
|
|
* group: { objectId: '$name' },
|
|
|
|
|
* }]
|
|
|
|
|
*
|
|
|
|
|
* Object
|
|
|
|
|
*
|
|
|
|
|
* body: {
|
|
|
|
|
* group: { objectId: '$name' },
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Pipeline Operator with an Array or an Object
|
|
|
|
|
*
|
|
|
|
|
* body: {
|
|
|
|
|
* pipeline: {
|
2024-10-22 21:51:58 +03:00
|
|
|
* $group: { objectId: '$name' },
|
2018-08-12 20:05:08 -05:00
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
static getPipeline(body) {
|
|
|
|
|
let pipeline = body.pipeline || body;
|
|
|
|
|
if (!Array.isArray(pipeline)) {
|
2024-10-22 21:51:58 +03:00
|
|
|
pipeline = Object.keys(pipeline)
|
|
|
|
|
.filter(key => pipeline[key] !== undefined)
|
|
|
|
|
.map(key => {
|
|
|
|
|
return { [key]: pipeline[key] };
|
|
|
|
|
});
|
2018-08-12 20:05:08 -05:00
|
|
|
}
|
|
|
|
|
|
2020-07-13 17:13:08 -05:00
|
|
|
return pipeline.map(stage => {
|
2018-08-12 20:05:08 -05:00
|
|
|
const keys = Object.keys(stage);
|
2023-01-06 01:53:43 +11:00
|
|
|
if (keys.length !== 1) {
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.INVALID_QUERY,
|
|
|
|
|
`Pipeline stages should only have one key but found ${keys.join(', ')}.`
|
|
|
|
|
);
|
2018-08-12 20:05:08 -05:00
|
|
|
}
|
|
|
|
|
return AggregateRouter.transformStage(keys[0], stage);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static transformStage(stageName, stage) {
|
2023-01-06 01:53:43 +11:00
|
|
|
const skipKeys = ['distinct', 'where'];
|
|
|
|
|
if (skipKeys.includes(stageName)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (stageName[0] !== '$') {
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid aggregate stage '${stageName}'.`);
|
|
|
|
|
}
|
|
|
|
|
if (stageName === '$group') {
|
2021-08-12 12:14:04 -05:00
|
|
|
if (Object.prototype.hasOwnProperty.call(stage[stageName], 'objectId')) {
|
2023-01-06 01:53:43 +11:00
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.INVALID_QUERY,
|
|
|
|
|
`Cannot use 'objectId' in aggregation stage $group.`
|
|
|
|
|
);
|
2018-06-23 11:28:17 -05:00
|
|
|
}
|
2021-08-12 12:14:04 -05:00
|
|
|
if (!Object.prototype.hasOwnProperty.call(stage[stageName], '_id')) {
|
2018-06-23 11:28:17 -05:00
|
|
|
throw new Parse.Error(
|
|
|
|
|
Parse.Error.INVALID_QUERY,
|
2021-08-12 12:14:04 -05:00
|
|
|
`Invalid parameter for query: group. Missing key _id`
|
2018-06-23 11:28:17 -05:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-06 01:53:43 +11:00
|
|
|
return { [stageName]: stage[stageName] };
|
2018-06-23 11:28:17 -05:00
|
|
|
}
|
|
|
|
|
|
2017-11-12 13:00:22 -06:00
|
|
|
mountRoutes() {
|
2020-10-25 15:06:58 -05:00
|
|
|
this.route('GET', '/aggregate/:className', middleware.promiseEnforceMasterKeyAccess, req => {
|
|
|
|
|
return this.handleFind(req);
|
|
|
|
|
});
|
2017-11-12 13:00:22 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default AggregateRouter;
|