BREAKING CHANGE: This upgrades the internally used Express framework from version 4 to 5, which may be a breaking change. If Parse Server is set up to be mounted on an Express application, we recommend to also use version 5 of the Express framework to avoid any compatibility issues. Note that even if there are no issues after upgrading, future releases of Parse Server may introduce issues if Parse Server internally relies on Express 5-specific features which are unsupported by the Express version on which it is mounted. See the Express [migration guide](https://expressjs.com/en/guide/migrating-5.html) and [release announcement](https://expressjs.com/2024/10/15/v5-release.html#breaking-changes) for more info.
252 lines
6.7 KiB
JavaScript
252 lines
6.7 KiB
JavaScript
import PromiseRouter from '../PromiseRouter';
|
|
import rest from '../rest';
|
|
import _ from 'lodash';
|
|
import Parse from 'parse/node';
|
|
import { promiseEnsureIdempotency } from '../middlewares';
|
|
|
|
const ALLOWED_GET_QUERY_KEYS = [
|
|
'keys',
|
|
'include',
|
|
'excludeKeys',
|
|
'readPreference',
|
|
'includeReadPreference',
|
|
'subqueryReadPreference',
|
|
];
|
|
|
|
export class ClassesRouter extends PromiseRouter {
|
|
className(req) {
|
|
return req.params.className;
|
|
}
|
|
|
|
handleFind(req) {
|
|
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
|
|
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
|
|
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
|
|
// Silently replace the limit on the query with the max configured
|
|
options.limit = Number(req.config.maxLimit);
|
|
}
|
|
if (body.redirectClassNameForKey) {
|
|
options.redirectClassNameForKey = String(body.redirectClassNameForKey);
|
|
}
|
|
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,
|
|
req.info.context
|
|
)
|
|
.then(response => {
|
|
return { response: response };
|
|
});
|
|
}
|
|
|
|
// Returns a promise for a {response} object.
|
|
handleGet(req) {
|
|
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
|
|
const options = {};
|
|
|
|
for (const key of Object.keys(body)) {
|
|
if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) {
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter');
|
|
}
|
|
}
|
|
|
|
if (body.keys != null) {
|
|
options.keys = String(body.keys);
|
|
}
|
|
if (body.include != null) {
|
|
options.include = String(body.include);
|
|
}
|
|
if (body.excludeKeys != null) {
|
|
options.excludeKeys = String(body.excludeKeys);
|
|
}
|
|
if (typeof body.readPreference === 'string') {
|
|
options.readPreference = body.readPreference;
|
|
}
|
|
if (typeof body.includeReadPreference === 'string') {
|
|
options.includeReadPreference = body.includeReadPreference;
|
|
}
|
|
if (typeof body.subqueryReadPreference === 'string') {
|
|
options.subqueryReadPreference = body.subqueryReadPreference;
|
|
}
|
|
|
|
return rest
|
|
.get(
|
|
req.config,
|
|
req.auth,
|
|
this.className(req),
|
|
req.params.objectId,
|
|
options,
|
|
req.info.clientSDK,
|
|
req.info.context
|
|
)
|
|
.then(response => {
|
|
if (!response.results || response.results.length == 0) {
|
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
|
}
|
|
|
|
if (this.className(req) === '_User') {
|
|
delete response.results[0].sessionToken;
|
|
|
|
const user = response.results[0];
|
|
|
|
if (req.auth.user && user.objectId == req.auth.user.id) {
|
|
// Force the session token
|
|
response.results[0].sessionToken = req.info.sessionToken;
|
|
}
|
|
}
|
|
return { response: response.results[0] };
|
|
});
|
|
}
|
|
|
|
handleCreate(req) {
|
|
if (
|
|
this.className(req) === '_User' &&
|
|
typeof req.body?.objectId === 'string' &&
|
|
req.body.objectId.startsWith('role:')
|
|
) {
|
|
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
|
}
|
|
return rest.create(
|
|
req.config,
|
|
req.auth,
|
|
this.className(req),
|
|
req.body || {},
|
|
req.info.clientSDK,
|
|
req.info.context
|
|
);
|
|
}
|
|
|
|
handleUpdate(req) {
|
|
const where = { objectId: req.params.objectId };
|
|
return rest.update(
|
|
req.config,
|
|
req.auth,
|
|
this.className(req),
|
|
where,
|
|
req.body || {},
|
|
req.info.clientSDK,
|
|
req.info.context
|
|
);
|
|
}
|
|
|
|
handleDelete(req) {
|
|
return rest
|
|
.del(req.config, req.auth, this.className(req), req.params.objectId, req.info.context)
|
|
.then(() => {
|
|
return { response: {} };
|
|
});
|
|
}
|
|
|
|
static JSONFromQuery(query) {
|
|
const json = {};
|
|
for (const [key, value] of _.entries(query)) {
|
|
try {
|
|
json[key] = JSON.parse(value);
|
|
} catch (e) {
|
|
json[key] = value;
|
|
}
|
|
}
|
|
return json;
|
|
}
|
|
|
|
static optionsFromBody(body, defaultLimit) {
|
|
const allowConstraints = [
|
|
'skip',
|
|
'limit',
|
|
'order',
|
|
'count',
|
|
'keys',
|
|
'excludeKeys',
|
|
'include',
|
|
'includeAll',
|
|
'redirectClassNameForKey',
|
|
'where',
|
|
'readPreference',
|
|
'includeReadPreference',
|
|
'subqueryReadPreference',
|
|
'hint',
|
|
'explain',
|
|
'comment',
|
|
];
|
|
|
|
for (const key of Object.keys(body)) {
|
|
if (allowConstraints.indexOf(key) === -1) {
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid parameter for query: ${key}`);
|
|
}
|
|
}
|
|
const options = {};
|
|
if (body.skip) {
|
|
options.skip = Number(body.skip);
|
|
}
|
|
if (body.limit || body.limit === 0) {
|
|
options.limit = Number(body.limit);
|
|
} else {
|
|
options.limit = Number(defaultLimit);
|
|
}
|
|
if (body.order) {
|
|
options.order = String(body.order);
|
|
}
|
|
if (body.count) {
|
|
options.count = true;
|
|
}
|
|
if (body.keys != null) {
|
|
options.keys = String(body.keys);
|
|
}
|
|
if (body.excludeKeys != null) {
|
|
options.excludeKeys = String(body.excludeKeys);
|
|
}
|
|
if (body.include != null) {
|
|
options.include = String(body.include);
|
|
}
|
|
if (body.includeAll) {
|
|
options.includeAll = true;
|
|
}
|
|
if (typeof body.readPreference === 'string') {
|
|
options.readPreference = body.readPreference;
|
|
}
|
|
if (typeof body.includeReadPreference === 'string') {
|
|
options.includeReadPreference = body.includeReadPreference;
|
|
}
|
|
if (typeof body.subqueryReadPreference === 'string') {
|
|
options.subqueryReadPreference = body.subqueryReadPreference;
|
|
}
|
|
if (body.hint && (typeof body.hint === 'string' || typeof body.hint === 'object')) {
|
|
options.hint = body.hint;
|
|
}
|
|
if (body.explain) {
|
|
options.explain = body.explain;
|
|
}
|
|
if (body.comment && typeof body.comment === 'string') {
|
|
options.comment = body.comment;
|
|
}
|
|
return options;
|
|
}
|
|
|
|
mountRoutes() {
|
|
this.route('GET', '/classes/:className', req => {
|
|
return this.handleFind(req);
|
|
});
|
|
this.route('GET', '/classes/:className/:objectId', req => {
|
|
return this.handleGet(req);
|
|
});
|
|
this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => {
|
|
return this.handleCreate(req);
|
|
});
|
|
this.route('PUT', '/classes/:className/:objectId', promiseEnsureIdempotency, req => {
|
|
return this.handleUpdate(req);
|
|
});
|
|
this.route('DELETE', '/classes/:className/:objectId', req => {
|
|
return this.handleDelete(req);
|
|
});
|
|
}
|
|
}
|
|
|
|
export default ClassesRouter;
|