mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e97579ea85 | ||
|
|
e5fa3b5e27 | ||
|
|
56f3e63112 | ||
|
|
263ca5e052 | ||
|
|
ddb0fb8a27 | ||
|
|
f9dca6072a | ||
|
|
ba0e529f81 | ||
|
|
109bc9a0ce | ||
|
|
90e9994195 | ||
|
|
97c124bbbe | ||
|
|
22c1a87d80 | ||
|
|
4c927dd898 | ||
|
|
408764a7b7 | ||
|
|
86a0f1ad02 | ||
|
|
0773523bc2 | ||
|
|
f6312a1b98 |
@@ -23,6 +23,9 @@ coverage
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# build folder for automated releases
|
||||
latest
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
+27
-11
@@ -4,6 +4,7 @@ node_js:
|
||||
- '6.1'
|
||||
services:
|
||||
- postgresql
|
||||
- redis-server
|
||||
addons:
|
||||
postgresql: '9.4'
|
||||
before_script:
|
||||
@@ -15,11 +16,14 @@ env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/istanbul cover'
|
||||
matrix:
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- MONGODB_VERSION=3.2.6
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: PARSE_SERVER_TEST_DB=postgres
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -30,14 +34,26 @@ cache:
|
||||
- "$HOME/.mongodb/versions"
|
||||
after_script:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
after_failure:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
after_success:
|
||||
- (cd resources; python travis_after_all.py)
|
||||
- export $(cat resources/.to_export_back)
|
||||
|
||||
deploy:
|
||||
provider: npm
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$MONGODB_VERSION = '3.0.8'"
|
||||
repo: ParsePlatform/parse-server
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: ./resources/npm-git.sh
|
||||
on:
|
||||
branch: master
|
||||
condition: "$BUILD_LEADER$BUILD_AGGREGATE_STATUS = YESothers_succeeded"
|
||||
- provider: npm
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
condition: "$MONGODB_VERSION = '3.0.8'"
|
||||
repo: ParsePlatform/parse-server
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.2.20
|
||||
|
||||
* New: Adds CloudCode handler for `beforeFind`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: RedisCacheAdapter for syncing schema, role and user caches across servers, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* New: Latest master build available at `ParsePlatform/parse-server#latest`, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Better support for upgradeToRevocableSession with missing session token, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Removes babel-polyfill runtime dependency, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Cluster option now support a boolean value for automatically choosing the right number of processes, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
* Fix: Filenames now appear correctly, thanks to [Lama Chandrasena](https://github.com/lama-buddy)
|
||||
* Fix: `_acl` is properly updated, thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
|
||||
Other fixes by [Mathias Rangel Wulff](https://github.com/mathiasrw)
|
||||
|
||||
### 2.2.19
|
||||
|
||||
* New: support for upgrading to revocable sessions, thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
@@ -266,7 +266,7 @@ var server = ParseServer({
|
||||
// if the account lockout policy is set and there are more than `threshold` number of failed login attempts then the `login` api call returns error code `Parse.Error.OBJECT_NOT_FOUND` with error message `Your account is locked due to multiple failed login attempts. Please try again after <duration> minute(s)`. After `duration` minutes of no login attempts, the application will allow the user to try login again.
|
||||
accountLockout: {
|
||||
duration: 5, // duration policy setting determines the number of minutes that a locked-out account remains locked out before automatically becoming unlocked. Set it to a value greater than 0 and less than 100000.
|
||||
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000.
|
||||
threshold: 3, // threshold policy setting determines the number of failed sign-in attempts that will cause a user account to be locked. Set it to an integer value greater than 0 and less than 1000.
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -278,6 +278,7 @@ You can also use other email adapters contributed by the community such as:
|
||||
- [parse-server-simple-ses-adapter](https://www.npmjs.com/package/parse-server-simple-ses-adapter)
|
||||
- [parse-server-mailgun-adapter-template](https://www.npmjs.com/package/parse-server-mailgun-adapter-template)
|
||||
- [parse-server-mailjet-adapter](https://www.npmjs.com/package/parse-server-mailjet-adapter)
|
||||
- [simple-parse-smtp-adapter](https://www.npmjs.com/package/simple-parse-smtp-adapter)
|
||||
|
||||
### Using environment variables to configure Parse Server
|
||||
|
||||
@@ -325,6 +326,18 @@ If you believe you've found an issue with Parse Server, make sure these boxes ar
|
||||
|
||||
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
|
||||
|
||||
# Want to ride the bleeding edge?
|
||||
|
||||
The `latest` branch in this repository is automatically maintained to be the last
|
||||
commit to `master` to pass all tests, in the same form found on npm. It is
|
||||
recommend to use builds deployed npm for many reasons, but if you want to use
|
||||
the latest not-yet-released version of parse-server, you can do so by depending
|
||||
directly on this branch:
|
||||
|
||||
```
|
||||
npm install parseplatform/parse-server.git#latest
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.19",
|
||||
"version": "2.2.20",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -18,7 +18,6 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"babel-polyfill": "6.13.0",
|
||||
"bcryptjs": "2.3.0",
|
||||
"body-parser": "1.15.2",
|
||||
"commander": "2.9.0",
|
||||
@@ -28,8 +27,9 @@
|
||||
"lodash": "4.15.0",
|
||||
"lru-cache": "4.0.1",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.9",
|
||||
"mongodb": "2.2.10",
|
||||
"multer": "1.2.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"parse": "1.9.1",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"parse-server-push-adapter": "1.1.0",
|
||||
@@ -37,11 +37,11 @@
|
||||
"parse-server-simple-mailgun-adapter": "1.0.0",
|
||||
"pg-promise": "5.3.3",
|
||||
"redis": "2.6.2",
|
||||
"request": "2.74.0",
|
||||
"request": "2.75.0",
|
||||
"semver": "5.2.0",
|
||||
"tv4": "1.2.7",
|
||||
"winston": "2.2.0",
|
||||
"winston-daily-rotate-file": "1.3.0",
|
||||
"winston-daily-rotate-file": "1.3.1",
|
||||
"ws": "1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"deep-diff": "0.3.4",
|
||||
"gaze": "1.1.1",
|
||||
"istanbul": "1.0.0-alpha.1",
|
||||
"jasmine": "2.4.1",
|
||||
"jasmine": "2.5.2",
|
||||
"mongodb-runner": "3.4.0",
|
||||
"nodemon": "1.10.0",
|
||||
"request-promise": "4.1.1"
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
# This script maintains a git branch which mirrors master but in a form that
|
||||
# what will eventually be deployed to npm, allowing npm dependencies to use:
|
||||
#
|
||||
# "parse-server": "parseplatform/parse-server#latest"
|
||||
#
|
||||
|
||||
# From: https://github.com/graphql/graphql-js/blob/master/resources/npm-git.sh
|
||||
|
||||
BUILD_DIR=latest
|
||||
|
||||
npm run build
|
||||
|
||||
mkdir -p $BUILD_DIR
|
||||
|
||||
cp package.json $BUILD_DIR/
|
||||
cp README.md $BUILD_DIR/
|
||||
cp LICENSE $BUILD_DIR/
|
||||
cp PATENTS $BUILD_DIR/
|
||||
cp CHANGELOG.md $BUILD_DIR/
|
||||
cp -R lib $BUILD_DIR
|
||||
cp -R bin $BUILD_DIR
|
||||
cp -R public_html $BUILD_DIR
|
||||
cp -R views $BUILD_DIR
|
||||
|
||||
cd $BUILD_DIR
|
||||
git init
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "github@fb.com"
|
||||
git add .
|
||||
git commit -m "Deploy master to LATEST branch"
|
||||
git push --force --quiet "https://${GH_TOKEN}@github.com/parseplatform/parse-server.git" master:latest
|
||||
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
https://github.com/dmakhno/travis_after_all/blob/master/travis_after_all.py
|
||||
https://github.com/graphql/graphql-js/blob/master/resources/travis_after_all.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
try:
|
||||
import urllib.request as urllib2
|
||||
except ImportError:
|
||||
import urllib2
|
||||
|
||||
log = logging.getLogger("travis.leader")
|
||||
log.addHandler(logging.StreamHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
TRAVIS_JOB_NUMBER = 'TRAVIS_JOB_NUMBER'
|
||||
TRAVIS_BUILD_ID = 'TRAVIS_BUILD_ID'
|
||||
POLLING_INTERVAL = 'LEADER_POLLING_INTERVAL'
|
||||
|
||||
build_id = os.getenv(TRAVIS_BUILD_ID)
|
||||
polling_interval = int(os.getenv(POLLING_INTERVAL, '5'))
|
||||
|
||||
#assume, first job is the leader
|
||||
is_leader = lambda job_number: job_number.endswith('.1')
|
||||
|
||||
if not os.getenv(TRAVIS_JOB_NUMBER):
|
||||
# seems even for builds with only one job, this won't get here
|
||||
log.fatal("Don't use defining leader for build without matrix")
|
||||
exit(1)
|
||||
elif is_leader(os.getenv(TRAVIS_JOB_NUMBER)):
|
||||
log.info("This is a leader")
|
||||
else:
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_MINION=YES")
|
||||
log.info("This is a minion")
|
||||
exit(0)
|
||||
|
||||
|
||||
class MatrixElement(object):
|
||||
def __init__(self, json_raw):
|
||||
self.is_finished = json_raw['finished_at'] is not None
|
||||
self.is_succeeded = json_raw['result'] == 0
|
||||
self.number = json_raw['number']
|
||||
self.is_leader = is_leader(self.number)
|
||||
|
||||
|
||||
def matrix_snapshot():
|
||||
"""
|
||||
:return: Matrix List
|
||||
"""
|
||||
response = urllib2.build_opener().open("https://api.travis-ci.org/builds/{0}".format(build_id)).read()
|
||||
raw_json = json.loads(response)
|
||||
matrix_without_leader = [MatrixElement(element) for element in raw_json["matrix"]]
|
||||
return matrix_without_leader
|
||||
|
||||
|
||||
def wait_others_to_finish():
|
||||
def others_finished():
|
||||
"""
|
||||
Dumps others to finish
|
||||
Leader cannot finish, it is working now
|
||||
:return: tuple(True or False, List of not finished jobs)
|
||||
"""
|
||||
snapshot = matrix_snapshot()
|
||||
finished = [el.is_finished for el in snapshot if not el.is_leader]
|
||||
return reduce(lambda a, b: a and b, finished), [el.number for el in snapshot if
|
||||
not el.is_leader and not el.is_finished]
|
||||
|
||||
while True:
|
||||
finished, waiting_list = others_finished()
|
||||
if finished: break
|
||||
log.info("Leader waits for minions {0}...".format(waiting_list)) # just in case do not get "silence timeout"
|
||||
time.sleep(polling_interval)
|
||||
|
||||
|
||||
try:
|
||||
wait_others_to_finish()
|
||||
|
||||
final_snapshot = matrix_snapshot()
|
||||
log.info("Final Results: {0}".format([(e.number, e.is_succeeded) for e in final_snapshot]))
|
||||
|
||||
BUILD_AGGREGATE_STATUS = 'BUILD_AGGREGATE_STATUS'
|
||||
others_snapshot = [el for el in final_snapshot if not el.is_leader]
|
||||
if reduce(lambda a, b: a and b, [e.is_succeeded for e in others_snapshot]):
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_succeeded"
|
||||
elif reduce(lambda a, b: a and b, [not e.is_succeeded for e in others_snapshot]):
|
||||
log.error("Others Failed")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "others_failed"
|
||||
else:
|
||||
log.warn("Others Unknown")
|
||||
os.environ[BUILD_AGGREGATE_STATUS] = "unknown"
|
||||
#since python is subprocess, env variables are exported back via file
|
||||
with open(".to_export_back", "w") as export_var:
|
||||
export_var.write("BUILD_LEADER=YES {0}={1}".format(BUILD_AGGREGATE_STATUS, os.environ[BUILD_AGGREGATE_STATUS]))
|
||||
|
||||
except Exception as e:
|
||||
log.fatal(e)
|
||||
@@ -1174,3 +1174,110 @@ it('beforeSave should not affect fetched pointers', done => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('beforeFind hooks', () => {
|
||||
it('should add beforeFind trigger', (done) => {
|
||||
Parse.Cloud.beforeFind('MyObject', (req, res) => {
|
||||
let q = req.query;
|
||||
expect(q instanceof Parse.Query).toBe(true);
|
||||
let jsonQuery = q.toJSON();
|
||||
expect(jsonQuery.where.key).toEqual('value');
|
||||
expect(jsonQuery.where.some).toEqual({'$gt': 10});
|
||||
expect(jsonQuery.include).toEqual('otherKey,otherValue');
|
||||
expect(jsonQuery.limit).toEqual(100);
|
||||
expect(jsonQuery.skip).toBe(undefined);
|
||||
});
|
||||
|
||||
let query = new Parse.Query('MyObject');
|
||||
query.equalTo('key', 'value');
|
||||
query.greaterThan('some', 10);
|
||||
query.include('otherKey');
|
||||
query.include('otherValue');
|
||||
query.find().then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use modify', (done) => {
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
let q = req.query;
|
||||
q.equalTo('forced', true);
|
||||
});
|
||||
|
||||
let obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('forced', false);
|
||||
|
||||
let obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('forced', true);
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
let query = new Parse.Query('MyObject');
|
||||
query.equalTo('forced', false);
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let firstResult = results[0];
|
||||
expect(firstResult.get('forced')).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the modified the query', (done) => {
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
let q = req.query;
|
||||
let otherQuery = new Parse.Query('MyObject');
|
||||
otherQuery.equalTo('forced', true);
|
||||
return Parse.Query.or(q, otherQuery);
|
||||
});
|
||||
|
||||
let obj0 = new Parse.Object('MyObject');
|
||||
obj0.set('forced', false);
|
||||
|
||||
let obj1 = new Parse.Object('MyObject');
|
||||
obj1.set('forced', true);
|
||||
Parse.Object.saveAll([obj0, obj1]).then(() => {
|
||||
let query = new Parse.Query('MyObject');
|
||||
query.equalTo('forced', false);
|
||||
query.find().then((results) => {
|
||||
expect(results.length).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject queries', (done) => {
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
return Promise.reject('Do not run that query');
|
||||
});
|
||||
|
||||
let query = new Parse.Query('MyObject');
|
||||
query.find().then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toBe(1);
|
||||
expect(err.message).toEqual('Do not run that query');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty where', (done) => {
|
||||
Parse.Cloud.beforeFind('MyObject', (req) => {
|
||||
let otherQuery = new Parse.Query('MyObject');
|
||||
otherQuery.equalTo('some', true);
|
||||
return Parse.Query.or(req.query, otherQuery);
|
||||
});
|
||||
|
||||
rp.get({
|
||||
url: 'http://localhost:8378/1/classes/MyObject',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
}).then((result) => {
|
||||
done();
|
||||
}, (err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm', (done) => {
|
||||
it('writes the old ACL format in addition to rperm and wperm on create', (done) => {
|
||||
var input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin'],
|
||||
@@ -229,10 +229,29 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
||||
|
||||
var output = transform.parseObjectToMongoObjectForCreate(null, input, { fields: {} });
|
||||
expect(typeof output._acl).toEqual('object');
|
||||
expect(output._acl["Kevin"].w).toBeTruthy();
|
||||
expect(output._acl["Kevin"].r).toBeUndefined();
|
||||
expect(output._acl['Kevin'].w).toBeTruthy();
|
||||
expect(output._acl['Kevin'].r).toBeUndefined();
|
||||
expect(output._rperm).toEqual(input._rperm);
|
||||
expect(output._wperm).toEqual(input._wperm);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('writes the old ACL format in addition to rperm and wperm on update', (done) => {
|
||||
var input = {
|
||||
_rperm: ['*'],
|
||||
_wperm: ['Kevin']
|
||||
};
|
||||
|
||||
var output = transform.transformUpdate(null, input, { fields: {} });
|
||||
var set = output.$set;
|
||||
expect(typeof set).toEqual('object');
|
||||
expect(typeof set._acl).toEqual('object');
|
||||
expect(set._acl['Kevin'].w).toBeTruthy();
|
||||
expect(set._acl['Kevin'].r).toBeUndefined();
|
||||
expect(set._rperm).toEqual(input._rperm);
|
||||
expect(set._wperm).toEqual(input._wperm);
|
||||
done();
|
||||
});
|
||||
|
||||
it('untransforms from _rperm and _wperm to ACL', (done) => {
|
||||
var input = {
|
||||
|
||||
@@ -89,4 +89,24 @@ describe_only_db('mongo')('revocable sessions', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not crash without session token #2720', done => {
|
||||
rp.post({
|
||||
url: Parse.serverURL+'/upgradeToRevocableSession',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-Rest-API-Key': 'rest'
|
||||
},
|
||||
json: true
|
||||
}).then((res) => {
|
||||
fail('should not be able to upgrade a bad token');
|
||||
}, (response) => {
|
||||
expect(response.statusCode).toBe(404);
|
||||
expect(response.error).not.toBeUndefined();
|
||||
expect(response.error.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
||||
expect(response.error.error).toEqual('invalid session');
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -24,6 +24,7 @@ var MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAda
|
||||
const GridStoreAdapter = require('../src/Adapters/Files/GridStoreAdapter').GridStoreAdapter;
|
||||
const FSAdapter = require('parse-server-fs-adapter');
|
||||
const PostgresStorageAdapter = require('../src/Adapters/Storage/Postgres/PostgresStorageAdapter');
|
||||
const RedisCacheAdapter = require('../src/Adapters/Cache/RedisCacheAdapter').default;
|
||||
|
||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||
@@ -101,6 +102,10 @@ var defaultConfiguration = {
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') {
|
||||
defaultConfiguration.cacheAdapter = new RedisCacheAdapter();
|
||||
}
|
||||
|
||||
let openConnections = {};
|
||||
|
||||
// Set up a default API server for testing with default configuration.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import redis from 'redis';
|
||||
import logger from '../../logger';
|
||||
|
||||
function debug() {
|
||||
logger.debug.apply(logger, ['RedisCacheAdapter', ...arguments]);
|
||||
}
|
||||
|
||||
export class RedisCacheAdapter {
|
||||
|
||||
constructor(ctx) {
|
||||
this.client = redis.createClient(ctx);
|
||||
this.p = Promise.resolve();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
debug('get', key);
|
||||
this.p = this.p.then(() => {
|
||||
return new Promise((resolve, _) => {
|
||||
this.client.get(key, function(err, res) {
|
||||
debug('-> get', key, res);
|
||||
if(!res) {
|
||||
return resolve(null);
|
||||
}
|
||||
resolve(JSON.parse(res));
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.p;
|
||||
}
|
||||
|
||||
put(key, value, ttl) {
|
||||
value = JSON.stringify(value);
|
||||
debug('put', key, value, ttl);
|
||||
this.p = this.p.then(() => {
|
||||
return new Promise((resolve, _) => {
|
||||
this.client.set(key, value, function(err, res) {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.p;
|
||||
}
|
||||
|
||||
del(key) {
|
||||
debug('del', key);
|
||||
this.p = this.p.then(() => {
|
||||
return new Promise((resolve, _) => {
|
||||
this.client.del(key, function(err, res) {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.p;
|
||||
}
|
||||
|
||||
clear() {
|
||||
debug('clear');
|
||||
this.p = this.p.then(() => {
|
||||
return new Promise((resolve, _) => {
|
||||
this.client.flushall(function(err, res) {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
return this.p;
|
||||
}
|
||||
}
|
||||
|
||||
export default RedisCacheAdapter;
|
||||
@@ -99,7 +99,7 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
|
||||
}
|
||||
|
||||
const transformInteriorValue = restValue => {
|
||||
if (restValue !== null && typeof restValue === 'object' && Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
|
||||
if (restValue !== null && typeof restValue === 'object' && Object.keys(restValue).some(key => key.indexOf('$') >= 0 || key.indexOf('.') >= 0)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||
}
|
||||
// Handle atomic values
|
||||
@@ -293,7 +293,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) =>
|
||||
}
|
||||
|
||||
// Handle normal objects by recursing
|
||||
if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
|
||||
if (Object.keys(restValue).some(key => key.indexOf('$') >= 0 || key.indexOf('.') >= 0)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||
}
|
||||
value = _.mapValues(restValue, transformInteriorValue);
|
||||
@@ -330,8 +330,8 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
|
||||
// Main exposed method to help update old objects.
|
||||
const transformUpdate = (className, restUpdate, parseFormatSchema) => {
|
||||
let mongoUpdate = {};
|
||||
let acl = addLegacyACL(restUpdate)._acl;
|
||||
if (acl) {
|
||||
let acl = addLegacyACL(restUpdate);
|
||||
if (acl._rperm || acl._wperm || acl._acl) {
|
||||
mongoUpdate.$set = {};
|
||||
if (acl._rperm) {
|
||||
mongoUpdate.$set._rperm = acl._rperm;
|
||||
|
||||
@@ -415,7 +415,7 @@ export class PostgresStorageAdapter {
|
||||
return toParseSchema(schema)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.code === PostgresUniqueIndexViolationError && err.detail.includes(className)) {
|
||||
if (err.code === PostgresUniqueIndexViolationError && err.detail.indexOf(className) >= 0) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`)
|
||||
}
|
||||
throw err;
|
||||
@@ -445,7 +445,7 @@ export class PostgresStorageAdapter {
|
||||
relations.push(fieldName)
|
||||
return;
|
||||
}
|
||||
if (['_rperm', '_wperm'].includes(fieldName)) {
|
||||
if (['_rperm', '_wperm'].indexOf(fieldName) >=0 ) {
|
||||
parseType.contents = { type: 'String' };
|
||||
}
|
||||
valuesArray.push(fieldName);
|
||||
@@ -678,7 +678,7 @@ export class PostgresStorageAdapter {
|
||||
valuesArray.push(object[fieldName].objectId);
|
||||
break;
|
||||
case 'Array':
|
||||
if (['_rperm', '_wperm'].includes(fieldName)) {
|
||||
if (['_rperm', '_wperm'].indexOf(fieldName) >= 0) {
|
||||
valuesArray.push(object[fieldName]);
|
||||
} else {
|
||||
valuesArray.push(JSON.stringify(object[fieldName]));
|
||||
@@ -707,7 +707,7 @@ export class PostgresStorageAdapter {
|
||||
let initialValues = valuesArray.map((val, index) => {
|
||||
let termination = '';
|
||||
let fieldName = columnsArray[index];
|
||||
if (['_rperm','_wperm'].includes(fieldName)) {
|
||||
if (['_rperm','_wperm'].indexOf(fieldName) >= 0) {
|
||||
termination = '::text[]';
|
||||
} else if (schema.fields[fieldName] && schema.fields[fieldName].type === 'Array') {
|
||||
termination = '::jsonb';
|
||||
@@ -1031,9 +1031,9 @@ export class PostgresStorageAdapter {
|
||||
const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join(',')})`;
|
||||
return this._client.none(qs,[className, constraintName, ...fieldNames])
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError && error.message.includes(constraintName)) {
|
||||
if (error.code === PostgresDuplicateRelationError && error.message.indexOf(constraintName) >= 0) {
|
||||
// Index already exists. Ignore error.
|
||||
} else if (error.code === PostgresUniqueIndexViolationError && error.message.includes(constraintName)) {
|
||||
} else if (error.code === PostgresUniqueIndexViolationError && error.message.indexOf(constraintName) >= 0) {
|
||||
// Cast the error into the proper parse error
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@ import AdaptableController from './AdaptableController';
|
||||
import { FilesAdapter } from '../Adapters/Files/FilesAdapter';
|
||||
import path from 'path';
|
||||
import mime from 'mime';
|
||||
import uuid from 'node-uuid';
|
||||
|
||||
const legacyFilesRegex = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*");
|
||||
|
||||
@@ -26,7 +27,7 @@ export class FilesController extends AdaptableController {
|
||||
contentType = mime.lookup(filename);
|
||||
}
|
||||
|
||||
filename = randomHexString(32) + '_' + filename;
|
||||
filename = uuid.v4() + '_' + filename;
|
||||
|
||||
var location = this.adapter.getFileLocation(config, filename);
|
||||
return this.adapter.createFile(filename, data, contentType).then(() => {
|
||||
|
||||
@@ -323,15 +323,20 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
reloadData(options = {clearCache: false}) {
|
||||
let promise = Promise.resolve();
|
||||
if (options.clearCache) {
|
||||
this._cache.clear();
|
||||
promise = promise.then(() => {
|
||||
return this._cache.clear();
|
||||
});
|
||||
}
|
||||
if (this.reloadDataPromise && !options.clearCache) {
|
||||
return this.reloadDataPromise;
|
||||
}
|
||||
this.data = {};
|
||||
this.perms = {};
|
||||
this.reloadDataPromise = this.getAllClasses(options)
|
||||
this.reloadDataPromise = promise.then(() => {
|
||||
return this.getAllClasses(options);
|
||||
})
|
||||
.then(allSchemas => {
|
||||
allSchemas.forEach(schema => {
|
||||
this.data[schema.className] = injectDefaultSchema(schema).fields;
|
||||
@@ -355,10 +360,13 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
getAllClasses(options = {clearCache: false}) {
|
||||
let promise = Promise.resolve();
|
||||
if (options.clearCache) {
|
||||
this._cache.clear();
|
||||
promise = this._cache.clear();
|
||||
}
|
||||
return this._cache.getAllClasses().then((allClasses) => {
|
||||
return promise.then(() => {
|
||||
return this._cache.getAllClasses()
|
||||
}).then((allClasses) => {
|
||||
if (allClasses && allClasses.length && !options.clearCache) {
|
||||
return Promise.resolve(allClasses);
|
||||
}
|
||||
@@ -373,22 +381,25 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
getOneSchema(className, allowVolatileClasses = false, options = {clearCache: false}) {
|
||||
let promise = Promise.resolve();
|
||||
if (options.clearCache) {
|
||||
this._cache.clear();
|
||||
promise = this._cache.clear();
|
||||
}
|
||||
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
|
||||
return Promise.resolve(this.data[className]);
|
||||
}
|
||||
return this._cache.getOneSchema(className).then((cached) => {
|
||||
if (cached && !options.clearCache) {
|
||||
return Promise.resolve(cached);
|
||||
return promise.then(() => {
|
||||
if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
|
||||
return Promise.resolve(this.data[className]);
|
||||
}
|
||||
return this._dbAdapter.getClass(className)
|
||||
.then(injectDefaultSchema)
|
||||
.then((result) => {
|
||||
return this._cache.setOneSchema(className, result).then(() => {
|
||||
return result;
|
||||
})
|
||||
return this._cache.getOneSchema(className).then((cached) => {
|
||||
if (cached && !options.clearCache) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
return this._dbAdapter.getClass(className)
|
||||
.then(injectDefaultSchema)
|
||||
.then((result) => {
|
||||
return this._cache.setOneSchema(className, result).then(() => {
|
||||
return result;
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -409,8 +420,9 @@ export default class SchemaController {
|
||||
return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className }))
|
||||
.then(convertAdapterSchemaToParseSchema)
|
||||
.then((res) => {
|
||||
this._cache.clear();
|
||||
return res;
|
||||
return this._cache.clear().then(() => {
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||
@@ -508,6 +520,7 @@ export default class SchemaController {
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
// The schema still doesn't validate. Give up
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||
});
|
||||
|
||||
@@ -9,10 +9,6 @@ var batch = require('./batch'),
|
||||
path = require('path'),
|
||||
authDataManager = require('./authDataManager');
|
||||
|
||||
if (!global._babelPolyfill) {
|
||||
require('babel-polyfill');
|
||||
}
|
||||
|
||||
import defaults from './defaults';
|
||||
import * as logging from './logger';
|
||||
import AppCache from './cache';
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) {
|
||||
memo.push(query);
|
||||
return memo;
|
||||
}, []).filter((q) => {
|
||||
return typeof q !== undefined;
|
||||
return typeof q !== 'undefined';
|
||||
});
|
||||
|
||||
let findPromise = Promise.resolve([]);
|
||||
|
||||
@@ -54,6 +54,11 @@ export class SessionsRouter extends ClassesRouter {
|
||||
const config = req.config;
|
||||
const masterAuth = Auth.master(config)
|
||||
const user = req.auth.user;
|
||||
// Issue #2720
|
||||
// Calling without a session token would result in a not found user
|
||||
if (!user) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'invalid session');
|
||||
}
|
||||
const expiresAt = config.generateSessionExpiresAt();
|
||||
const sessionData = {
|
||||
sessionToken: 'r:' + newToken(),
|
||||
|
||||
@@ -8,6 +8,15 @@ function numberParser(key) {
|
||||
}
|
||||
}
|
||||
|
||||
function numberOrBoolParser(key) {
|
||||
return function(opt) {
|
||||
if (typeof opt === 'boolean') {
|
||||
return opt;
|
||||
}
|
||||
return numberParser(key)(opt);
|
||||
}
|
||||
}
|
||||
|
||||
function objectParser(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
@@ -229,6 +238,6 @@ export default {
|
||||
},
|
||||
"cluster": {
|
||||
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
|
||||
action: numberParser("cluster"),
|
||||
action: numberOrBoolParser("cluster")
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,6 +45,11 @@ ParseCloud.afterDelete = function(parseClass, handler) {
|
||||
triggers.addTrigger(triggers.Types.afterDelete, className, handler, Parse.applicationId);
|
||||
};
|
||||
|
||||
ParseCloud.beforeFind = function(parseClass, handler) {
|
||||
var className = getClassName(parseClass);
|
||||
triggers.addTrigger(triggers.Types.beforeFind, className, handler, Parse.applicationId);
|
||||
};
|
||||
|
||||
ParseCloud._removeAllHooks = () => {
|
||||
triggers._unregisterAll();
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import S3Adapter from 'parse-server-s3-adapter'
|
||||
import FileSystemAdapter from 'parse-server-fs-adapter'
|
||||
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
|
||||
import NullCacheAdapter from './Adapters/Cache/NullCacheAdapter'
|
||||
import RedisCacheAdapter from './Adapters/Cache/RedisCacheAdapter'
|
||||
import TestUtils from './TestUtils';
|
||||
import { useExternal } from './deprecated';
|
||||
import { getLogger } from './logger';
|
||||
@@ -22,4 +23,4 @@ Object.defineProperty(module.exports, 'logger', {
|
||||
});
|
||||
|
||||
export default ParseServer;
|
||||
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, TestUtils, _ParseServer as ParseServer };
|
||||
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, NullCacheAdapter, RedisCacheAdapter, TestUtils, _ParseServer as ParseServer };
|
||||
|
||||
+6
-2
@@ -17,8 +17,12 @@ var triggers = require('./triggers');
|
||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||
function find(config, auth, className, restWhere, restOptions, clientSDK) {
|
||||
enforceRoleSecurity('find', className, auth);
|
||||
let query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||
return query.execute();
|
||||
return triggers.maybeRunQueryTrigger(triggers.Types.beforeFind, className, restWhere, restOptions, config, auth).then((result) => {
|
||||
restWhere = result.restWhere || restWhere;
|
||||
restOptions = result.restOptions || restOptions;
|
||||
let query = new RestQuery(config, auth, className, restWhere, restOptions, clientSDK);
|
||||
return query.execute();
|
||||
});
|
||||
}
|
||||
|
||||
// get is just like find but only queries an objectId.
|
||||
|
||||
+85
-1
@@ -7,7 +7,8 @@ export const Types = {
|
||||
beforeSave: 'beforeSave',
|
||||
afterSave: 'afterSave',
|
||||
beforeDelete: 'beforeDelete',
|
||||
afterDelete: 'afterDelete'
|
||||
afterDelete: 'afterDelete',
|
||||
beforeFind: 'beforeFind'
|
||||
};
|
||||
|
||||
const baseStore = function() {
|
||||
@@ -154,6 +155,29 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
|
||||
return request;
|
||||
}
|
||||
|
||||
export function getRequestQueryObject(triggerType, auth, query, config) {
|
||||
var request = {
|
||||
triggerName: triggerType,
|
||||
query: query,
|
||||
master: false,
|
||||
log: config.loggerController
|
||||
};
|
||||
|
||||
if (!auth) {
|
||||
return request;
|
||||
}
|
||||
if (auth.isMaster) {
|
||||
request['master'] = true;
|
||||
}
|
||||
if (auth.user) {
|
||||
request['user'] = auth.user;
|
||||
}
|
||||
if (auth.installationId) {
|
||||
request['installationId'] = auth.installationId;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
// Creates the response object, and uses the request object to pass data
|
||||
// The API will call this with REST API formatted objects, this will
|
||||
// transform them to Parse.Object instances expected by Cloud Code.
|
||||
@@ -216,6 +240,66 @@ function logTriggerErrorBeforeHook(triggerType, className, input, auth, error) {
|
||||
});
|
||||
}
|
||||
|
||||
export function maybeRunQueryTrigger(triggerType, className, restWhere, restOptions, config, auth) {
|
||||
let trigger = getTrigger(className, triggerType, config.applicationId);
|
||||
if (!trigger) {
|
||||
return Promise.resolve({
|
||||
restWhere,
|
||||
restOptions
|
||||
});
|
||||
}
|
||||
|
||||
let parseQuery = new Parse.Query(className);
|
||||
if (restWhere) {
|
||||
parseQuery._where = restWhere;
|
||||
}
|
||||
if (restOptions) {
|
||||
if (restOptions.include && restOptions.include.length > 0) {
|
||||
parseQuery._include = restOptions.include.split(',');
|
||||
}
|
||||
if (restOptions.skip) {
|
||||
parseQuery._skip = restOptions.skip;
|
||||
}
|
||||
if (restOptions.limit) {
|
||||
parseQuery._limit = restOptions.limit;
|
||||
}
|
||||
}
|
||||
let requestObject = getRequestQueryObject(triggerType, auth, parseQuery, config);
|
||||
return Promise.resolve().then(() => {
|
||||
return trigger(requestObject);
|
||||
}).then((result) => {
|
||||
let queryResult = parseQuery;
|
||||
if (result && result instanceof Parse.Query) {
|
||||
queryResult = result;
|
||||
}
|
||||
let jsonQuery = queryResult.toJSON();
|
||||
if (jsonQuery.where) {
|
||||
restWhere = jsonQuery.where;
|
||||
}
|
||||
if (jsonQuery.limit) {
|
||||
restOptions = restOptions || {};
|
||||
restOptions.limit = jsonQuery.limit;
|
||||
}
|
||||
if (jsonQuery.skip) {
|
||||
restOptions = restOptions || {};
|
||||
restOptions.skip = jsonQuery.skip;
|
||||
}
|
||||
if (jsonQuery.include) {
|
||||
restOptions = restOptions || {};
|
||||
restOptions.include = jsonQuery.include;
|
||||
}
|
||||
return {
|
||||
restWhere,
|
||||
restOptions
|
||||
};
|
||||
}, (err) => {
|
||||
if (typeof err === 'string') {
|
||||
throw new Parse.Error(1, err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// To be used as part of the promise chain when saving/deleting an object
|
||||
// Will resolve successfully if no trigger is configured
|
||||
|
||||
Reference in New Issue
Block a user