Merge branch 'master' into test_for_calling_nonexisting_cf
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -28,3 +28,6 @@ node_modules
|
|||||||
|
|
||||||
# Emacs
|
# Emacs
|
||||||
*~
|
*~
|
||||||
|
|
||||||
|
# WebStorm/IntelliJ
|
||||||
|
.idea
|
||||||
11
.travis.yml
Normal file
11
.travis.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "4.1"
|
||||||
|
- "4.2"
|
||||||
|
env:
|
||||||
|
- MONGODB_VERSION=2.6.11
|
||||||
|
- MONGODB_VERSION=3.0.8
|
||||||
|
after_success: ./node_modules/.bin/codecov
|
||||||
1
Auth.js
1
Auth.js
@@ -64,6 +64,7 @@ var getAuthForSessionToken = function(config, sessionToken) {
|
|||||||
var obj = results[0]['user'];
|
var obj = results[0]['user'];
|
||||||
delete obj.password;
|
delete obj.password;
|
||||||
obj['className'] = '_User';
|
obj['className'] = '_User';
|
||||||
|
obj['sessionToken'] = sessionToken;
|
||||||
var userObject = Parse.Object.fromJSON(obj);
|
var userObject = Parse.Object.fromJSON(obj);
|
||||||
cache.setUser(sessionToken, userObject);
|
cache.setUser(sessionToken, userObject);
|
||||||
return new Auth(config, false, userObject);
|
return new Auth(config, false, userObject);
|
||||||
|
|||||||
17
CONTRIBUTING.md
Normal file
17
CONTRIBUTING.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
### Contributing to Parse Server
|
||||||
|
|
||||||
|
#### Pull Requests Welcome!
|
||||||
|
|
||||||
|
We really want Parse to be yours, to see it grow and thrive in the open source community.
|
||||||
|
|
||||||
|
##### Please Do's
|
||||||
|
|
||||||
|
* Please write tests to cover new methods.
|
||||||
|
* Please run the tests and make sure you didn't break anything.
|
||||||
|
|
||||||
|
##### Code of Conduct
|
||||||
|
|
||||||
|
This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
|
||||||
|
[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ var adapter = ExportAdapter;
|
|||||||
var cache = require('./cache');
|
var cache = require('./cache');
|
||||||
var dbConnections = {};
|
var dbConnections = {};
|
||||||
var databaseURI = 'mongodb://localhost:27017/parse';
|
var databaseURI = 'mongodb://localhost:27017/parse';
|
||||||
|
var appDatabaseURIs = {};
|
||||||
|
|
||||||
function setAdapter(databaseAdapter) {
|
function setAdapter(databaseAdapter) {
|
||||||
adapter = databaseAdapter;
|
adapter = databaseAdapter;
|
||||||
@@ -29,11 +30,17 @@ function setDatabaseURI(uri) {
|
|||||||
databaseURI = uri;
|
databaseURI = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAppDatabaseURI(appId, uri) {
|
||||||
|
appDatabaseURIs[appId] = uri;
|
||||||
|
}
|
||||||
|
|
||||||
function getDatabaseConnection(appId) {
|
function getDatabaseConnection(appId) {
|
||||||
if (dbConnections[appId]) {
|
if (dbConnections[appId]) {
|
||||||
return dbConnections[appId];
|
return dbConnections[appId];
|
||||||
}
|
}
|
||||||
dbConnections[appId] = new adapter(databaseURI, {
|
|
||||||
|
var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI);
|
||||||
|
dbConnections[appId] = new adapter(dbURI, {
|
||||||
collectionPrefix: cache.apps[appId]['collectionPrefix']
|
collectionPrefix: cache.apps[appId]['collectionPrefix']
|
||||||
});
|
});
|
||||||
dbConnections[appId].connect();
|
dbConnections[appId].connect();
|
||||||
@@ -44,5 +51,6 @@ module.exports = {
|
|||||||
dbConnections: dbConnections,
|
dbConnections: dbConnections,
|
||||||
getDatabaseConnection: getDatabaseConnection,
|
getDatabaseConnection: getDatabaseConnection,
|
||||||
setAdapter: setAdapter,
|
setAdapter: setAdapter,
|
||||||
setDatabaseURI: setDatabaseURI
|
setDatabaseURI: setDatabaseURI,
|
||||||
|
setAppDatabaseURI: setAppDatabaseURI
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,8 +34,21 @@ ExportAdapter.prototype.connect = function() {
|
|||||||
return this.connectionPromise;
|
return this.connectionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//http://regexr.com/3cncm
|
||||||
|
if (!this.mongoURI.match(/^mongodb:\/\/((.+):(.+)@)?([^:@]+):{0,1}([^:]+)\/(.+?)$/gm)) {
|
||||||
|
throw new Error("Invalid mongoURI: " + this.mongoURI)
|
||||||
|
}
|
||||||
|
var usernameStart = this.mongoURI.indexOf('://') + 3;
|
||||||
|
var lastAtIndex = this.mongoURI.lastIndexOf('@');
|
||||||
|
var encodedMongoURI = this.mongoURI;
|
||||||
|
var split = null;
|
||||||
|
if (lastAtIndex > 0) {
|
||||||
|
split = this.mongoURI.slice(usernameStart, lastAtIndex).split(':');
|
||||||
|
encodedMongoURI = this.mongoURI.slice(0, usernameStart) + encodeURIComponent(split[0]) + ':' + encodeURIComponent(split[1]) + this.mongoURI.slice(lastAtIndex);
|
||||||
|
}
|
||||||
|
|
||||||
this.connectionPromise = Promise.resolve().then(() => {
|
this.connectionPromise = Promise.resolve().then(() => {
|
||||||
return MongoClient.connect(this.mongoURI);
|
return MongoClient.connect(encodedMongoURI, {uri_decode_auth:true});
|
||||||
}).then((db) => {
|
}).then((db) => {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
});
|
});
|
||||||
@@ -232,7 +245,7 @@ ExportAdapter.prototype.handleRelationUpdates = function(className,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (op.__op == 'Batch') {
|
if (op.__op == 'Batch') {
|
||||||
for (x of op.ops) {
|
for (var x of op.ops) {
|
||||||
process(x, key);
|
process(x, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// Adapter classes must implement the following functions:
|
// Adapter classes must implement the following functions:
|
||||||
// * create(config, filename, data)
|
// * create(config, filename, data)
|
||||||
// * get(config, filename)
|
// * get(config, filename)
|
||||||
|
// * location(config, req, filename)
|
||||||
//
|
//
|
||||||
// Default is GridStoreAdapter, which requires mongo
|
// Default is GridStoreAdapter, which requires mongo
|
||||||
// and for the API server to be using the ExportAdapter
|
// and for the API server to be using the ExportAdapter
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// Requires the database adapter to be based on mongoclient
|
// Requires the database adapter to be based on mongoclient
|
||||||
|
|
||||||
var GridStore = require('mongodb').GridStore;
|
var GridStore = require('mongodb').GridStore;
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
// For a given config object, filename, and data, store a file
|
// For a given config object, filename, and data, store a file
|
||||||
// Returns a promise
|
// Returns a promise
|
||||||
@@ -32,7 +33,16 @@ function get(config, filename) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates and returns the location of a file stored in GridStore for the
|
||||||
|
// given request and filename
|
||||||
|
function location(config, req, filename) {
|
||||||
|
return (req.protocol + '://' + req.get('host') +
|
||||||
|
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
|
||||||
|
'/' + encodeURIComponent(filename));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
create: create,
|
create: create,
|
||||||
get: get
|
get: get,
|
||||||
|
location: location
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
## parse-server
|
## parse-server
|
||||||
|
|
||||||
|
[](https://travis-ci.org/ParsePlatform/parse-server)
|
||||||
|
[](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
|
||||||
|
[](https://www.npmjs.com/package/parse-server)
|
||||||
|
|
||||||
A Parse.com API compatible router package for Express
|
A Parse.com API compatible router package for Express
|
||||||
|
|
||||||
Read the announcement blog post here: http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/
|
Read the announcement blog post here: http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ function includePath(config, auth, response, path) {
|
|||||||
function findPointers(object, path) {
|
function findPointers(object, path) {
|
||||||
if (object instanceof Array) {
|
if (object instanceof Array) {
|
||||||
var answer = [];
|
var answer = [];
|
||||||
for (x of object) {
|
for (var x of object) {
|
||||||
answer = answer.concat(findPointers(x, path));
|
answer = answer.concat(findPointers(x, path));
|
||||||
}
|
}
|
||||||
return answer;
|
return answer;
|
||||||
|
|||||||
40
RestWrite.js
40
RestWrite.js
@@ -2,13 +2,14 @@
|
|||||||
// that writes to the database.
|
// that writes to the database.
|
||||||
// This could be either a "create" or an "update".
|
// This could be either a "create" or an "update".
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
var deepcopy = require('deepcopy');
|
var deepcopy = require('deepcopy');
|
||||||
var rack = require('hat').rack();
|
var rack = require('hat').rack();
|
||||||
|
|
||||||
var Auth = require('./Auth');
|
var Auth = require('./Auth');
|
||||||
var cache = require('./cache');
|
var cache = require('./cache');
|
||||||
var Config = require('./Config');
|
var Config = require('./Config');
|
||||||
var crypto = require('./crypto');
|
var passwordCrypto = require('./password');
|
||||||
var facebook = require('./facebook');
|
var facebook = require('./facebook');
|
||||||
var Parse = require('parse/node');
|
var Parse = require('parse/node');
|
||||||
var triggers = require('./triggers');
|
var triggers = require('./triggers');
|
||||||
@@ -228,6 +229,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
|||||||
this.className,
|
this.className,
|
||||||
{'authData.facebook.id': facebookData.id}, {});
|
{'authData.facebook.id': facebookData.id}, {});
|
||||||
}).then((results) => {
|
}).then((results) => {
|
||||||
|
this.storage['authProvider'] = "facebook";
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
if (!this.query) {
|
if (!this.query) {
|
||||||
// We're signing up, but this user already exists. Short-circuit
|
// We're signing up, but this user already exists. Short-circuit
|
||||||
@@ -236,6 +238,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
|||||||
response: results[0],
|
response: results[0],
|
||||||
location: this.location()
|
location: this.location()
|
||||||
};
|
};
|
||||||
|
this.data.objectId = results[0].objectId;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +251,8 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
|||||||
// We're trying to create a duplicate FB auth. Forbid it
|
// We're trying to create a duplicate FB auth. Forbid it
|
||||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||||
'this auth is already used');
|
'this auth is already used');
|
||||||
|
} else {
|
||||||
|
this.data.username = rack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This FB auth does not already exist, so transform it to a
|
// This FB auth does not already exist, so transform it to a
|
||||||
@@ -261,7 +266,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
|
|||||||
|
|
||||||
// The non-third-party parts of User transformation
|
// The non-third-party parts of User transformation
|
||||||
RestWrite.prototype.transformUser = function() {
|
RestWrite.prototype.transformUser = function() {
|
||||||
if (this.response || this.className !== '_User') {
|
if (this.className !== '_User') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +276,8 @@ RestWrite.prototype.transformUser = function() {
|
|||||||
var token = 'r:' + rack();
|
var token = 'r:' + rack();
|
||||||
this.storage['token'] = token;
|
this.storage['token'] = token;
|
||||||
promise = promise.then(() => {
|
promise = promise.then(() => {
|
||||||
// TODO: Proper createdWith options, pass installationId
|
var expiresAt = new Date();
|
||||||
|
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
|
||||||
var sessionData = {
|
var sessionData = {
|
||||||
sessionToken: token,
|
sessionToken: token,
|
||||||
user: {
|
user: {
|
||||||
@@ -281,10 +287,15 @@ RestWrite.prototype.transformUser = function() {
|
|||||||
},
|
},
|
||||||
createdWith: {
|
createdWith: {
|
||||||
'action': 'login',
|
'action': 'login',
|
||||||
'authProvider': 'password'
|
'authProvider': this.storage['authProvider'] || 'password'
|
||||||
},
|
},
|
||||||
restricted: false
|
restricted: false,
|
||||||
|
installationId: this.data.installationId,
|
||||||
|
expiresAt: Parse._encode(expiresAt)
|
||||||
};
|
};
|
||||||
|
if (this.response && this.response.response) {
|
||||||
|
this.response.response.sessionToken = token;
|
||||||
|
}
|
||||||
var create = new RestWrite(this.config, Auth.master(this.config),
|
var create = new RestWrite(this.config, Auth.master(this.config),
|
||||||
'_Session', null, sessionData);
|
'_Session', null, sessionData);
|
||||||
return create.execute();
|
return create.execute();
|
||||||
@@ -299,7 +310,7 @@ RestWrite.prototype.transformUser = function() {
|
|||||||
if (this.query) {
|
if (this.query) {
|
||||||
this.storage['clearSessions'] = true;
|
this.storage['clearSessions'] = true;
|
||||||
}
|
}
|
||||||
return crypto.hash(this.data.password).then((hashedPassword) => {
|
return passwordCrypto.hash(this.data.password).then((hashedPassword) => {
|
||||||
this.data._hashed_password = hashedPassword;
|
this.data._hashed_password = hashedPassword;
|
||||||
delete this.data.password;
|
delete this.data.password;
|
||||||
});
|
});
|
||||||
@@ -361,7 +372,7 @@ RestWrite.prototype.handleFollowup = function() {
|
|||||||
};
|
};
|
||||||
delete this.storage['clearSessions'];
|
delete this.storage['clearSessions'];
|
||||||
return this.config.database.destroy('_Session', sessionQuery)
|
return this.config.database.destroy('_Session', sessionQuery)
|
||||||
.then(this.handleFollowup);
|
.then(this.handleFollowup.bind(this));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -403,6 +414,8 @@ RestWrite.prototype.handleSession = function() {
|
|||||||
|
|
||||||
if (!this.query && !this.auth.isMaster) {
|
if (!this.query && !this.auth.isMaster) {
|
||||||
var token = 'r:' + rack();
|
var token = 'r:' + rack();
|
||||||
|
var expiresAt = new Date();
|
||||||
|
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
|
||||||
var sessionData = {
|
var sessionData = {
|
||||||
sessionToken: token,
|
sessionToken: token,
|
||||||
user: {
|
user: {
|
||||||
@@ -414,7 +427,7 @@ RestWrite.prototype.handleSession = function() {
|
|||||||
'action': 'create'
|
'action': 'create'
|
||||||
},
|
},
|
||||||
restricted: true,
|
restricted: true,
|
||||||
expiresAt: 0
|
expiresAt: Parse._encode(expiresAt)
|
||||||
};
|
};
|
||||||
for (var key in this.data) {
|
for (var key in this.data) {
|
||||||
if (key == 'objectId') {
|
if (key == 'objectId') {
|
||||||
@@ -701,15 +714,18 @@ RestWrite.prototype.objectId = function() {
|
|||||||
return this.data.objectId || this.query.objectId;
|
return this.data.objectId || this.query.objectId;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a string that's usable as an object id.
|
// Returns a unique string that's usable as an object id.
|
||||||
// Probably unique. Good enough? Probably!
|
|
||||||
function newObjectId() {
|
function newObjectId() {
|
||||||
var chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
var chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
|
||||||
'abcdefghijklmnopqrstuvwxyz' +
|
'abcdefghijklmnopqrstuvwxyz' +
|
||||||
'0123456789');
|
'0123456789');
|
||||||
var objectId = '';
|
var objectId = '';
|
||||||
for (var i = 0; i < 10; ++i) {
|
var bytes = crypto.randomBytes(10);
|
||||||
objectId += chars[Math.floor(Math.random() * chars.length)];
|
for (var i = 0; i < bytes.length; ++i) {
|
||||||
|
// Note: there is a slight modulo bias, because chars length
|
||||||
|
// of 62 doesn't divide the number of all bytes (256) evenly.
|
||||||
|
// It is acceptable for our purposes.
|
||||||
|
objectId += chars[bytes.readUInt8(i) % chars.length];
|
||||||
}
|
}
|
||||||
return objectId;
|
return objectId;
|
||||||
}
|
}
|
||||||
|
|||||||
77
S3Adapter.js
Normal file
77
S3Adapter.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// S3Adapter
|
||||||
|
//
|
||||||
|
// Stores Parse files in AWS S3.
|
||||||
|
|
||||||
|
var AWS = require('aws-sdk');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var DEFAULT_REGION = "us-east-1";
|
||||||
|
var DEFAULT_BUCKET = "parse-files";
|
||||||
|
|
||||||
|
// Creates an S3 session.
|
||||||
|
// Providing AWS access and secret keys is mandatory
|
||||||
|
// Region and bucket will use sane defaults if omitted
|
||||||
|
function S3Adapter(accessKey, secretKey, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
this.region = options.region || DEFAULT_REGION;
|
||||||
|
this.bucket = options.bucket || DEFAULT_BUCKET;
|
||||||
|
this.bucketPrefix = options.bucketPrefix || "";
|
||||||
|
this.directAccess = options.directAccess || false;
|
||||||
|
|
||||||
|
s3Options = {
|
||||||
|
accessKeyId: accessKey,
|
||||||
|
secretAccessKey: secretKey,
|
||||||
|
params: {Bucket: this.bucket}
|
||||||
|
};
|
||||||
|
AWS.config.region = this.region;
|
||||||
|
this.s3 = new AWS.S3(s3Options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a given config object, filename, and data, store a file in S3
|
||||||
|
// Returns a promise containing the S3 object creation response
|
||||||
|
S3Adapter.prototype.create = function(config, filename, data) {
|
||||||
|
var params = {
|
||||||
|
Key: this.bucketPrefix + filename,
|
||||||
|
Body: data,
|
||||||
|
};
|
||||||
|
if (this.directAccess) {
|
||||||
|
params.ACL = "public-read"
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.s3.upload(params, (err, data) => {
|
||||||
|
if (err !== null) return reject(err);
|
||||||
|
resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for and return a file if found by filename
|
||||||
|
// Returns a promise that succeeds with the buffer result from S3
|
||||||
|
S3Adapter.prototype.get = function(config, filename) {
|
||||||
|
var params = {Key: this.bucketPrefix + filename};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.s3.getObject(params, (err, data) => {
|
||||||
|
if (err !== null) return reject(err);
|
||||||
|
resolve(data.Body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates and returns the location of a file stored in S3 for the given request and
|
||||||
|
// filename
|
||||||
|
// The location is the direct S3 link if the option is set, otherwise we serve
|
||||||
|
// the file through parse-server
|
||||||
|
S3Adapter.prototype.location = function(config, req, filename) {
|
||||||
|
if (this.directAccess) {
|
||||||
|
return ('https://' + this.bucket + '.s3.amazonaws.com' + '/' +
|
||||||
|
this.bucketPrefix + filename);
|
||||||
|
}
|
||||||
|
return (req.protocol + '://' + req.get('host') +
|
||||||
|
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
|
||||||
|
'/' + encodeURIComponent(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = S3Adapter;
|
||||||
@@ -212,6 +212,9 @@ Schema.prototype.validateObject = function(className, object) {
|
|||||||
var geocount = 0;
|
var geocount = 0;
|
||||||
var promise = this.validateClassName(className);
|
var promise = this.validateClassName(className);
|
||||||
for (var key in object) {
|
for (var key in object) {
|
||||||
|
if (object[key] === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var expected = getType(object[key]);
|
var expected = getType(object[key]);
|
||||||
if (expected === 'geopoint') {
|
if (expected === 'geopoint') {
|
||||||
geocount++;
|
geocount++;
|
||||||
|
|||||||
41
classes.js
41
classes.js
@@ -10,32 +10,45 @@ var router = new PromiseRouter();
|
|||||||
|
|
||||||
// Returns a promise that resolves to a {response} object.
|
// Returns a promise that resolves to a {response} object.
|
||||||
function handleFind(req) {
|
function handleFind(req) {
|
||||||
|
var body = Object.assign(req.body, req.query);
|
||||||
var options = {};
|
var options = {};
|
||||||
if (req.body.skip) {
|
if (body.skip) {
|
||||||
options.skip = Number(req.body.skip);
|
options.skip = Number(body.skip);
|
||||||
}
|
}
|
||||||
if (req.body.limit) {
|
if (body.limit) {
|
||||||
options.limit = Number(req.body.limit);
|
options.limit = Number(body.limit);
|
||||||
}
|
}
|
||||||
if (req.body.order) {
|
if (body.order) {
|
||||||
options.order = String(req.body.order);
|
options.order = String(body.order);
|
||||||
}
|
}
|
||||||
if (req.body.count) {
|
if (body.count) {
|
||||||
options.count = true;
|
options.count = true;
|
||||||
}
|
}
|
||||||
if (typeof req.body.keys == 'string') {
|
if (typeof body.keys == 'string') {
|
||||||
options.keys = req.body.keys;
|
options.keys = body.keys;
|
||||||
}
|
}
|
||||||
if (req.body.include) {
|
if (body.include) {
|
||||||
options.include = String(req.body.include);
|
options.include = String(body.include);
|
||||||
}
|
}
|
||||||
if (req.body.redirectClassNameForKey) {
|
if (body.redirectClassNameForKey) {
|
||||||
options.redirectClassNameForKey = String(req.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,
|
return rest.find(req.config, req.auth,
|
||||||
req.params.className, req.body.where, options)
|
req.params.className, body.where, options)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
if (response && response.results) {
|
||||||
|
for (result of response.results) {
|
||||||
|
if (result.sessionToken) {
|
||||||
|
result.sessionToken = req.info.sessionToken || result.sessionToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.results.sessionToken
|
||||||
|
}
|
||||||
return {response: response};
|
return {response: response};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,19 @@ Parse.Cloud.define('hello', function(req, res) {
|
|||||||
res.success('Hello world!');
|
res.success('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
Parse.Cloud.beforeSave('BeforeSaveFailure', function(req, res) {
|
Parse.Cloud.beforeSave('BeforeSaveFail', function(req, res) {
|
||||||
res.error('You shall not pass!');
|
res.error('You shall not pass!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('BeforeSaveFailWithPromise', function (req, res) {
|
||||||
|
var query = new Parse.Query('Yolo');
|
||||||
|
query.find().then(() => {
|
||||||
|
res.error('Nope');
|
||||||
|
}, () => {
|
||||||
|
res.success();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Parse.Cloud.beforeSave('BeforeSaveUnchanged', function(req, res) {
|
Parse.Cloud.beforeSave('BeforeSaveUnchanged', function(req, res) {
|
||||||
res.success();
|
res.success();
|
||||||
});
|
});
|
||||||
@@ -27,6 +36,15 @@ Parse.Cloud.beforeDelete('BeforeDeleteFail', function(req, res) {
|
|||||||
res.error('Nope');
|
res.error('Nope');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('BeforeDeleteFailWithPromise', function (req, res) {
|
||||||
|
var query = new Parse.Query('Yolo');
|
||||||
|
query.find().then(() => {
|
||||||
|
res.error('Nope');
|
||||||
|
}, () => {
|
||||||
|
res.success();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Parse.Cloud.beforeDelete('BeforeDeleteTest', function(req, res) {
|
Parse.Cloud.beforeDelete('BeforeDeleteTest', function(req, res) {
|
||||||
res.success();
|
res.success();
|
||||||
});
|
});
|
||||||
|
|||||||
8
files.js
8
files.js
@@ -7,7 +7,6 @@ var bodyParser = require('body-parser'),
|
|||||||
middlewares = require('./middlewares.js'),
|
middlewares = require('./middlewares.js'),
|
||||||
mime = require('mime'),
|
mime = require('mime'),
|
||||||
Parse = require('parse/node').Parse,
|
Parse = require('parse/node').Parse,
|
||||||
path = require('path'),
|
|
||||||
rack = require('hat').rack();
|
rack = require('hat').rack();
|
||||||
|
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
@@ -44,10 +43,7 @@ var processCreate = function(req, res, next) {
|
|||||||
FilesAdapter.getAdapter().create(req.config, filename, req.body)
|
FilesAdapter.getAdapter().create(req.config, filename, req.body)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
res.status(201);
|
res.status(201);
|
||||||
var location = (req.protocol + '://' + req.get('host') +
|
var location = FilesAdapter.getAdapter().location(req.config, req, filename);
|
||||||
path.dirname(req.originalUrl) + '/' +
|
|
||||||
req.config.applicationId + '/' +
|
|
||||||
encodeURIComponent(filename));
|
|
||||||
res.set('Location', location);
|
res.set('Location', location);
|
||||||
res.json({ url: location, name: filename });
|
res.json({ url: location, name: filename });
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
@@ -78,8 +74,8 @@ router.post('/files', function(req, res, next) {
|
|||||||
'Filename not provided.'));
|
'Filename not provided.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: do we need to allow crossdomain and method override?
|
|
||||||
router.post('/files/:filename',
|
router.post('/files/:filename',
|
||||||
|
middlewares.allowCrossDomain,
|
||||||
bodyParser.raw({type: '*/*', limit: '20mb'}),
|
bodyParser.raw({type: '*/*', limit: '20mb'}),
|
||||||
middlewares.handleParseHeaders,
|
middlewares.handleParseHeaders,
|
||||||
processCreate);
|
processCreate);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ var express = require('express'),
|
|||||||
var router = new PromiseRouter();
|
var router = new PromiseRouter();
|
||||||
|
|
||||||
function handleCloudFunction(req) {
|
function handleCloudFunction(req) {
|
||||||
// TODO: set user from req.auth
|
|
||||||
if (Parse.Cloud.Functions[req.params.functionName]) {
|
if (Parse.Cloud.Functions[req.params.functionName]) {
|
||||||
// Run the validator for this function first
|
// Run the validator for this function first
|
||||||
if (Parse.Cloud.Validators[req.params.functionName]) {
|
if (Parse.Cloud.Validators[req.params.functionName]) {
|
||||||
@@ -21,7 +20,9 @@ function handleCloudFunction(req) {
|
|||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var response = createResponseObject(resolve, reject);
|
var response = createResponseObject(resolve, reject);
|
||||||
var request = {
|
var request = {
|
||||||
params: req.body || {}
|
params: req.body || {},
|
||||||
|
master: req.auth && req.auth.isMaster,
|
||||||
|
user: req.auth && req.auth.user,
|
||||||
};
|
};
|
||||||
Parse.Cloud.Functions[req.params.functionName](request, response);
|
Parse.Cloud.Functions[req.params.functionName](request, response);
|
||||||
});
|
});
|
||||||
|
|||||||
43
httpRequest.js
Normal file
43
httpRequest.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
var request = require("request"),
|
||||||
|
Parse = require('parse/node').Parse;
|
||||||
|
|
||||||
|
module.exports = function(options) {
|
||||||
|
var promise = new Parse.Promise();
|
||||||
|
var callbacks = {
|
||||||
|
success: options.success,
|
||||||
|
error: options.error
|
||||||
|
};
|
||||||
|
delete options.success;
|
||||||
|
delete options.error;
|
||||||
|
if (options.uri && !options.url) {
|
||||||
|
options.uri = options.url;
|
||||||
|
delete options.url;
|
||||||
|
}
|
||||||
|
if (typeof options.body === 'object') {
|
||||||
|
options.body = JSON.stringify(options.body);
|
||||||
|
}
|
||||||
|
request(options, (error, response, body) => {
|
||||||
|
var httpResponse = {};
|
||||||
|
httpResponse.status = response.statusCode;
|
||||||
|
httpResponse.headers = response.headers;
|
||||||
|
httpResponse.buffer = new Buffer(response.body);
|
||||||
|
httpResponse.cookies = response.headers["set-cookie"];
|
||||||
|
httpResponse.text = response.body;
|
||||||
|
try {
|
||||||
|
httpResponse.data = JSON.parse(response.body);
|
||||||
|
} catch (e) {}
|
||||||
|
// Consider <200 && >= 400 as errors
|
||||||
|
if (error || httpResponse.status <200 || httpResponse.status >=400) {
|
||||||
|
if (callbacks.error) {
|
||||||
|
return callbacks.error(httpResponse);
|
||||||
|
}
|
||||||
|
return promise.reject(httpResponse);
|
||||||
|
} else {
|
||||||
|
if (callbacks.success) {
|
||||||
|
return callbacks.success(httpResponse);
|
||||||
|
}
|
||||||
|
return promise.resolve(httpResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
51
index.js
51
index.js
@@ -6,11 +6,12 @@ var batch = require('./batch'),
|
|||||||
DatabaseAdapter = require('./DatabaseAdapter'),
|
DatabaseAdapter = require('./DatabaseAdapter'),
|
||||||
express = require('express'),
|
express = require('express'),
|
||||||
FilesAdapter = require('./FilesAdapter'),
|
FilesAdapter = require('./FilesAdapter'),
|
||||||
|
S3Adapter = require('./S3Adapter'),
|
||||||
middlewares = require('./middlewares'),
|
middlewares = require('./middlewares'),
|
||||||
multer = require('multer'),
|
multer = require('multer'),
|
||||||
Parse = require('parse/node').Parse,
|
Parse = require('parse/node').Parse,
|
||||||
PromiseRouter = require('./PromiseRouter'),
|
PromiseRouter = require('./PromiseRouter'),
|
||||||
request = require('request');
|
httpRequest = require('./httpRequest');
|
||||||
|
|
||||||
// Mutate the Parse object to add the Cloud Code handlers
|
// Mutate the Parse object to add the Cloud Code handlers
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
@@ -23,7 +24,9 @@ addParseCloud();
|
|||||||
// and delete
|
// and delete
|
||||||
// "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us
|
// "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us
|
||||||
// what database this Parse API connects to.
|
// what database this Parse API connects to.
|
||||||
// "cloud": relative location to cloud code to require
|
// "cloud": relative location to cloud code to require, or a function
|
||||||
|
// that is given an instance of Parse as a parameter. Use this instance of Parse
|
||||||
|
// to register your cloud code hooks and functions.
|
||||||
// "appId": the application id to host
|
// "appId": the application id to host
|
||||||
// "masterKey": the master key for requests to this app
|
// "masterKey": the master key for requests to this app
|
||||||
// "facebookAppIds": an array of valid Facebook Application IDs, required
|
// "facebookAppIds": an array of valid Facebook Application IDs, required
|
||||||
@@ -47,11 +50,18 @@ function ParseServer(args) {
|
|||||||
FilesAdapter.setAdapter(args.filesAdapter);
|
FilesAdapter.setAdapter(args.filesAdapter);
|
||||||
}
|
}
|
||||||
if (args.databaseURI) {
|
if (args.databaseURI) {
|
||||||
DatabaseAdapter.setDatabaseURI(args.databaseURI);
|
DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI);
|
||||||
}
|
}
|
||||||
if (args.cloud) {
|
if (args.cloud) {
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
require(args.cloud);
|
if (typeof args.cloud === 'function') {
|
||||||
|
args.cloud(Parse)
|
||||||
|
} else if (typeof args.cloud === 'string') {
|
||||||
|
require(args.cloud);
|
||||||
|
} else {
|
||||||
|
throw "argument 'cloud' must either be a string or a function";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.apps[args.appId] = {
|
cache.apps[args.appId] = {
|
||||||
@@ -101,6 +111,7 @@ function ParseServer(args) {
|
|||||||
router.merge(require('./push'));
|
router.merge(require('./push'));
|
||||||
router.merge(require('./installations'));
|
router.merge(require('./installations'));
|
||||||
router.merge(require('./functions'));
|
router.merge(require('./functions'));
|
||||||
|
router.merge(require('./schemas'));
|
||||||
|
|
||||||
batch.mountOnto(router);
|
batch.mountOnto(router);
|
||||||
|
|
||||||
@@ -141,33 +152,7 @@ function addParseCloud() {
|
|||||||
var className = getClassName(parseClass);
|
var className = getClassName(parseClass);
|
||||||
Parse.Cloud.Triggers.afterDelete[className] = handler;
|
Parse.Cloud.Triggers.afterDelete[className] = handler;
|
||||||
};
|
};
|
||||||
Parse.Cloud.httpRequest = function(options) {
|
Parse.Cloud.httpRequest = httpRequest;
|
||||||
var promise = new Parse.Promise();
|
|
||||||
var callbacks = {
|
|
||||||
success: options.success,
|
|
||||||
error: options.error
|
|
||||||
};
|
|
||||||
delete options.success;
|
|
||||||
delete options.error;
|
|
||||||
if (options.uri && !options.url) {
|
|
||||||
options.uri = options.url;
|
|
||||||
delete options.url;
|
|
||||||
}
|
|
||||||
request(options, (error, response, body) => {
|
|
||||||
if (error) {
|
|
||||||
if (callbacks.error) {
|
|
||||||
return callbacks.error(error);
|
|
||||||
}
|
|
||||||
return promise.reject(error);
|
|
||||||
} else {
|
|
||||||
if (callbacks.success) {
|
|
||||||
return callbacks.success(body);
|
|
||||||
}
|
|
||||||
return promise.resolve(body);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
global.Parse = Parse;
|
global.Parse = Parse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +164,6 @@ function getClassName(parseClass) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ParseServer: ParseServer
|
ParseServer: ParseServer,
|
||||||
|
S3Adapter: S3Adapter
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parse-server",
|
"name": "parse-server",
|
||||||
"version": "2.0.3",
|
"version": "2.0.6",
|
||||||
"description": "An express module providing a Parse-compatible API server",
|
"description": "An express module providing a Parse-compatible API server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
},
|
},
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "~0.8",
|
"aws-sdk": "~2.2.33",
|
||||||
|
"bcrypt-nodejs": "0.0.3",
|
||||||
"body-parser": "~1.12.4",
|
"body-parser": "~1.12.4",
|
||||||
"deepcopy": "^0.5.0",
|
"deepcopy": "^0.5.0",
|
||||||
"express": "~4.2.x",
|
"express": "~4.2.x",
|
||||||
@@ -21,10 +22,16 @@
|
|||||||
"request": "^2.65.0"
|
"request": "^2.65.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jasmine": "^2.3.2"
|
"codecov": "^1.0.1",
|
||||||
|
"deep-diff": "^0.3.3",
|
||||||
|
"istanbul": "^0.4.2",
|
||||||
|
"jasmine": "^2.3.2",
|
||||||
|
"mongodb-runner": "^3.1.15"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "TESTING=1 jasmine"
|
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
|
||||||
|
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
|
||||||
|
"posttest": "mongodb-runner stop"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.1"
|
"node": ">=4.1"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Tools for encrypting and decrypting passwords.
|
// Tools for encrypting and decrypting passwords.
|
||||||
// Basically promise-friendly wrappers for bcrypt.
|
// Basically promise-friendly wrappers for bcrypt.
|
||||||
var bcrypt = require('bcrypt');
|
var bcrypt = require('bcrypt-nodejs');
|
||||||
|
|
||||||
// Returns a promise for a hashed password string.
|
// Returns a promise for a hashed password string.
|
||||||
function hash(password) {
|
function hash(password) {
|
||||||
return new Promise(function(fulfill, reject) {
|
return new Promise(function(fulfill, reject) {
|
||||||
bcrypt.hash(password, 8, function(err, hashedPassword) {
|
bcrypt.hash(password, null, null, function(err, hashedPassword) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
69
schemas.js
Normal file
69
schemas.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// schemas.js
|
||||||
|
|
||||||
|
var express = require('express'),
|
||||||
|
PromiseRouter = require('./PromiseRouter');
|
||||||
|
|
||||||
|
var router = new PromiseRouter();
|
||||||
|
|
||||||
|
function mongoFieldTypeToApiResponseType(type) {
|
||||||
|
if (type[0] === '*') {
|
||||||
|
return {
|
||||||
|
type: 'Pointer',
|
||||||
|
targetClass: type.slice(1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (type.startsWith('relation<')) {
|
||||||
|
return {
|
||||||
|
type: 'Relation',
|
||||||
|
targetClass: type.slice('relation<'.length, type.length - 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'number': return {type: 'Number'};
|
||||||
|
case 'string': return {type: 'String'};
|
||||||
|
case 'boolean': return {type: 'Boolean'};
|
||||||
|
case 'date': return {type: 'Date'};
|
||||||
|
case 'object': return {type: 'Object'};
|
||||||
|
case 'array': return {type: 'Array'};
|
||||||
|
case 'geopoint': return {type: 'GeoPoint'};
|
||||||
|
case 'file': return {type: 'File'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mongoSchemaAPIResponseFields(schema) {
|
||||||
|
fieldNames = Object.keys(schema).filter(key => key !== '_id');
|
||||||
|
response = {};
|
||||||
|
fieldNames.forEach(fieldName => {
|
||||||
|
response[fieldName] = mongoFieldTypeToApiResponseType(schema[fieldName]);
|
||||||
|
});
|
||||||
|
response.ACL = {type: 'ACL'};
|
||||||
|
response.createdAt = {type: 'Date'};
|
||||||
|
response.updatedAt = {type: 'Date'};
|
||||||
|
response.objectId = {type: 'String'};
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mongoSchemaToSchemaAPIResponse(schema) {
|
||||||
|
return {
|
||||||
|
className: schema._id,
|
||||||
|
fields: mongoSchemaAPIResponseFields(schema),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllSchemas(req) {
|
||||||
|
if (!req.auth.isMaster) {
|
||||||
|
return Promise.resolve({
|
||||||
|
status: 401,
|
||||||
|
response: {error: 'unauthorized'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return req.config.database.collection('_SCHEMA')
|
||||||
|
.then(coll => coll.find({}).toArray())
|
||||||
|
.then(schemas => ({response: {
|
||||||
|
results: schemas.map(mongoSchemaToSchemaAPIResponse)
|
||||||
|
}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
router.route('GET', '/schemas', getAllSchemas);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -153,12 +153,26 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('basic beforeSave rejection', function(done) {
|
it('basic beforeSave rejection', function(done) {
|
||||||
var obj = new Parse.Object('BeforeSaveFailure');
|
var obj = new Parse.Object('BeforeSaveFail');
|
||||||
|
obj.set('foo', 'bar');
|
||||||
|
obj.save().then(() => {
|
||||||
|
fail('Should not have been able to save BeforeSaveFailure class.');
|
||||||
|
done();
|
||||||
|
}, () => {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('basic beforeSave rejection via promise', function(done) {
|
||||||
|
var obj = new Parse.Object('BeforeSaveFailWithPromise');
|
||||||
obj.set('foo', 'bar');
|
obj.set('foo', 'bar');
|
||||||
obj.save().then(function() {
|
obj.save().then(function() {
|
||||||
fail('Should not have been able to save BeforeSaveFailure class.');
|
fail('Should not have been able to save BeforeSaveFailure class.');
|
||||||
done();
|
done();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
|
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
|
||||||
|
expect(error.message).toEqual('Nope');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -250,6 +264,20 @@ describe('miscellaneous', function() {
|
|||||||
// We should have been able to fetch the object again
|
// We should have been able to fetch the object again
|
||||||
fail(error);
|
fail(error);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
it('basic beforeDelete rejection via promise', function(done) {
|
||||||
|
var obj = new Parse.Object('BeforeDeleteFailWithPromise');
|
||||||
|
obj.set('foo', 'bar');
|
||||||
|
obj.save().then(function() {
|
||||||
|
fail('Should not have been able to save BeforeSaveFailure class.');
|
||||||
|
done();
|
||||||
|
}, function(error) {
|
||||||
|
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
|
||||||
|
expect(error.message).toEqual('Nope');
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test beforeDelete success', function(done) {
|
it('test beforeDelete success', function(done) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Tests that involve sending password reset emails.
|
// Tests that involve sending password reset emails.
|
||||||
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var crypto = require('../crypto');
|
var passwordCrypto = require('../password');
|
||||||
|
|
||||||
describe('Parse.User testing', () => {
|
describe('Parse.User testing', () => {
|
||||||
it("user sign up class method", (done) => {
|
it("user sign up class method", (done) => {
|
||||||
@@ -78,7 +78,8 @@ describe('Parse.User testing', () => {
|
|||||||
sessionToken = newUser.getSessionToken();
|
sessionToken = newUser.getSessionToken();
|
||||||
ok(sessionToken);
|
ok(sessionToken);
|
||||||
|
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
ok(!Parse.User.current());
|
ok(!Parse.User.current());
|
||||||
|
|
||||||
return Parse.User.become(sessionToken);
|
return Parse.User.become(sessionToken);
|
||||||
@@ -91,7 +92,8 @@ describe('Parse.User testing', () => {
|
|||||||
equal(newUser.get("username"), "Jason");
|
equal(newUser.get("username"), "Jason");
|
||||||
equal(newUser.get("code"), "red");
|
equal(newUser.get("code"), "red");
|
||||||
|
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
ok(!Parse.User.current());
|
ok(!Parse.User.current());
|
||||||
|
|
||||||
return Parse.User.become("somegarbage");
|
return Parse.User.become("somegarbage");
|
||||||
@@ -236,22 +238,20 @@ describe('Parse.User testing', () => {
|
|||||||
user.set("password", "asdf");
|
user.set("password", "asdf");
|
||||||
user.set("email", "asdf@example.com");
|
user.set("email", "asdf@example.com");
|
||||||
user.set("username", "zxcv");
|
user.set("username", "zxcv");
|
||||||
user.signUp(null, {
|
user.signUp().then(() => {
|
||||||
success: function() {
|
var currentUser = Parse.User.current();
|
||||||
var currentUser = Parse.User.current();
|
equal(user.id, currentUser.id);
|
||||||
equal(user.id, currentUser.id);
|
ok(user.getSessionToken());
|
||||||
ok(user.getSessionToken());
|
|
||||||
|
|
||||||
var currentUserAgain = Parse.User.current();
|
var currentUserAgain = Parse.User.current();
|
||||||
// should be the same object
|
// should be the same object
|
||||||
equal(currentUser, currentUserAgain);
|
equal(currentUser, currentUserAgain);
|
||||||
|
|
||||||
// test logging out the current user
|
// test logging out the current user
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
equal(Parse.User.current(), null);
|
equal(Parse.User.current(), null);
|
||||||
done();
|
done();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -268,50 +268,39 @@ describe('Parse.User testing', () => {
|
|||||||
user2.set("password", "password");
|
user2.set("password", "password");
|
||||||
user3.set("password", "password");
|
user3.set("password", "password");
|
||||||
|
|
||||||
user1.signUp(null, {
|
user1.signUp().then(() => {
|
||||||
success: function () {
|
equal(user1.isCurrent(), true);
|
||||||
equal(user1.isCurrent(), true);
|
equal(user2.isCurrent(), false);
|
||||||
equal(user2.isCurrent(), false);
|
equal(user3.isCurrent(), false);
|
||||||
equal(user3.isCurrent(), false);
|
return user2.signUp();
|
||||||
user2.signUp(null, {
|
}).then(() => {
|
||||||
success: function() {
|
equal(user1.isCurrent(), false);
|
||||||
equal(user1.isCurrent(), false);
|
equal(user2.isCurrent(), true);
|
||||||
equal(user2.isCurrent(), true);
|
equal(user3.isCurrent(), false);
|
||||||
equal(user3.isCurrent(), false);
|
return user3.signUp();
|
||||||
user3.signUp(null, {
|
}).then(() => {
|
||||||
success: function() {
|
equal(user1.isCurrent(), false);
|
||||||
equal(user1.isCurrent(), false);
|
equal(user2.isCurrent(), false);
|
||||||
equal(user2.isCurrent(), false);
|
equal(user3.isCurrent(), true);
|
||||||
equal(user3.isCurrent(), true);
|
return Parse.User.logIn("a", "password");
|
||||||
Parse.User.logIn("a", "password", {
|
}).then(() => {
|
||||||
success: function(user1) {
|
equal(user1.isCurrent(), true);
|
||||||
equal(user1.isCurrent(), true);
|
equal(user2.isCurrent(), false);
|
||||||
equal(user2.isCurrent(), false);
|
equal(user3.isCurrent(), false);
|
||||||
equal(user3.isCurrent(), false);
|
return Parse.User.logIn("b", "password");
|
||||||
Parse.User.logIn("b", "password", {
|
}).then(() => {
|
||||||
success: function(user2) {
|
equal(user1.isCurrent(), false);
|
||||||
equal(user1.isCurrent(), false);
|
equal(user2.isCurrent(), true);
|
||||||
equal(user2.isCurrent(), true);
|
equal(user3.isCurrent(), false);
|
||||||
equal(user3.isCurrent(), false);
|
return Parse.User.logIn("b", "password");
|
||||||
Parse.User.logIn("b", "password", {
|
}).then(() => {
|
||||||
success: function(user3) {
|
equal(user1.isCurrent(), false);
|
||||||
equal(user1.isCurrent(), false);
|
equal(user2.isCurrent(), true);
|
||||||
equal(user2.isCurrent(), true);
|
equal(user3.isCurrent(), false);
|
||||||
equal(user3.isCurrent(), true);
|
return Parse.User.logOut();
|
||||||
Parse.User.logOut();
|
}).then(() => {
|
||||||
equal(user3.isCurrent(), false);
|
equal(user2.isCurrent(), false);
|
||||||
done();
|
done();
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -589,28 +578,24 @@ describe('Parse.User testing', () => {
|
|||||||
|
|
||||||
|
|
||||||
it("user loaded from localStorage from login", (done) => {
|
it("user loaded from localStorage from login", (done) => {
|
||||||
|
var id;
|
||||||
|
Parse.User.signUp("alice", "password").then((alice) => {
|
||||||
|
id = alice.id;
|
||||||
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn("alice", "password");
|
||||||
|
}).then((user) => {
|
||||||
|
// Force the current user to read from disk
|
||||||
|
delete Parse.User._currentUser;
|
||||||
|
delete Parse.User._currentUserMatchesDisk;
|
||||||
|
|
||||||
Parse.User.signUp("alice", "password", null, {
|
var userFromDisk = Parse.User.current();
|
||||||
success: function(alice) {
|
equal(userFromDisk.get("password"), undefined,
|
||||||
var id = alice.id;
|
"password should not be in attributes");
|
||||||
Parse.User.logOut();
|
equal(userFromDisk.id, id, "id should be set");
|
||||||
|
ok(userFromDisk.getSessionToken(),
|
||||||
Parse.User.logIn("alice", "password", {
|
"currentUser should have a sessionToken");
|
||||||
success: function(user) {
|
done();
|
||||||
// Force the current user to read from disk
|
|
||||||
delete Parse.User._currentUser;
|
|
||||||
delete Parse.User._currentUserMatchesDisk;
|
|
||||||
|
|
||||||
var userFromDisk = Parse.User.current();
|
|
||||||
equal(userFromDisk.get("password"), undefined,
|
|
||||||
"password should not be in attributes");
|
|
||||||
equal(userFromDisk.id, id, "id should be set");
|
|
||||||
ok(userFromDisk.getSessionToken(),
|
|
||||||
"currentUser should have a sessionToken");
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -620,8 +605,8 @@ describe('Parse.User testing', () => {
|
|||||||
|
|
||||||
Parse.User.signUp("alice", "password", null).then(function(alice) {
|
Parse.User.signUp("alice", "password", null).then(function(alice) {
|
||||||
id = alice.id;
|
id = alice.id;
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
return Parse.User.logIn("alice", "password");
|
return Parse.User.logIn("alice", "password");
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
// Simulate browser refresh by force-reloading user from localStorage
|
// Simulate browser refresh by force-reloading user from localStorage
|
||||||
@@ -1306,13 +1291,13 @@ describe('Parse.User testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("querying for users doesn't get session tokens", (done) => {
|
notWorking("querying for users doesn't get session tokens", (done) => {
|
||||||
Parse.Promise.as().then(function() {
|
Parse.Promise.as().then(function() {
|
||||||
return Parse.User.signUp("finn", "human", { foo: "bar" });
|
return Parse.User.signUp("finn", "human", { foo: "bar" });
|
||||||
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
var user = new Parse.User();
|
var user = new Parse.User();
|
||||||
user.set("username", "jake");
|
user.set("username", "jake");
|
||||||
user.set("password", "dog");
|
user.set("password", "dog");
|
||||||
@@ -1320,8 +1305,8 @@ describe('Parse.User testing', () => {
|
|||||||
return user.signUp();
|
return user.signUp();
|
||||||
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
Parse.User.logOut();
|
return Parse.User.logOut();
|
||||||
|
}).then(() => {
|
||||||
var query = new Parse.Query(Parse.User);
|
var query = new Parse.Query(Parse.User);
|
||||||
return query.find();
|
return query.find();
|
||||||
|
|
||||||
@@ -1351,7 +1336,7 @@ describe('Parse.User testing', () => {
|
|||||||
var b = JSON.parse(body);
|
var b = JSON.parse(body);
|
||||||
expect(b.results.length).toEqual(1);
|
expect(b.results.length).toEqual(1);
|
||||||
var user = b.results[0];
|
var user = b.results[0];
|
||||||
expect(Object.keys(user).length).toEqual(5);
|
expect(Object.keys(user).length).toEqual(6);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1560,7 +1545,7 @@ describe('Parse.User testing', () => {
|
|||||||
|
|
||||||
it('password format matches hosted parse', (done) => {
|
it('password format matches hosted parse', (done) => {
|
||||||
var hashed = '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie';
|
var hashed = '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie';
|
||||||
crypto.compare('test', hashed)
|
passwordCrypto.compare('test', hashed)
|
||||||
.then((pass) => {
|
.then((pass) => {
|
||||||
expect(pass).toBe(true);
|
expect(pass).toBe(true);
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Sets up a Parse API server for testing.
|
// Sets up a Parse API server for testing.
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
||||||
|
|
||||||
var cache = require('../cache');
|
var cache = require('../cache');
|
||||||
var DatabaseAdapter = require('../DatabaseAdapter');
|
var DatabaseAdapter = require('../DatabaseAdapter');
|
||||||
@@ -46,8 +46,7 @@ beforeEach(function(done) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function(done) {
|
afterEach(function(done) {
|
||||||
Parse.User.logOut();
|
Parse.User.logOut().then(() => {
|
||||||
Parse.Promise.as().then(() => {
|
|
||||||
return clearData();
|
return clearData();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
done();
|
done();
|
||||||
@@ -153,7 +152,7 @@ function normalize(obj) {
|
|||||||
return '[' + obj.map(normalize).join(', ') + ']';
|
return '[' + obj.map(normalize).join(', ') + ']';
|
||||||
}
|
}
|
||||||
var answer = '{';
|
var answer = '{';
|
||||||
for (key of Object.keys(obj).sort()) {
|
for (var key of Object.keys(obj).sort()) {
|
||||||
answer += key + ': ';
|
answer += key + ': ';
|
||||||
answer += normalize(obj[key]);
|
answer += normalize(obj[key]);
|
||||||
answer += ', ';
|
answer += ', ';
|
||||||
@@ -192,7 +191,7 @@ function mockFacebook() {
|
|||||||
|
|
||||||
function clearData() {
|
function clearData() {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for (conn in DatabaseAdapter.dbConnections) {
|
for (var conn in DatabaseAdapter.dbConnections) {
|
||||||
promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything());
|
promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything());
|
||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
|
|||||||
110
spec/schemas.spec.js
Normal file
110
spec/schemas.spec.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
var request = require('request');
|
||||||
|
var dd = require('deep-diff');
|
||||||
|
|
||||||
|
describe('schemas', () => {
|
||||||
|
it('requires the master key to get all schemas', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(401);
|
||||||
|
expect(body.error).toEqual('unauthorized');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with empty list when there are no schemas', done => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.results).toEqual([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with a list of schemas after creating objects', done => {
|
||||||
|
var obj1 = new Parse.Object('HasAllPOD');
|
||||||
|
obj1.set('aNumber', 5);
|
||||||
|
obj1.set('aString', 'string');
|
||||||
|
obj1.set('aBool', true);
|
||||||
|
obj1.set('aDate', new Date());
|
||||||
|
obj1.set('aObject', {k1: 'value', k2: true, k3: 5});
|
||||||
|
obj1.set('aArray', ['contents', true, 5]);
|
||||||
|
obj1.set('aGeoPoint', new Parse.GeoPoint({latitude: 0, longitude: 0}));
|
||||||
|
obj1.set('aFile', new Parse.File('f.txt', { base64: 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=' }));
|
||||||
|
var obj1ACL = new Parse.ACL();
|
||||||
|
obj1ACL.setPublicWriteAccess(false);
|
||||||
|
obj1.setACL(obj1ACL);
|
||||||
|
|
||||||
|
obj1.save().then(savedObj1 => {
|
||||||
|
var obj2 = new Parse.Object('HasPointersAndRelations');
|
||||||
|
obj2.set('aPointer', savedObj1);
|
||||||
|
var relation = obj2.relation('aRelation');
|
||||||
|
relation.add(obj1);
|
||||||
|
return obj2.save();
|
||||||
|
}).then(() => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
var expected = {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
className: 'HasAllPOD',
|
||||||
|
fields: {
|
||||||
|
//Default fields
|
||||||
|
ACL: {type: 'ACL'},
|
||||||
|
createdAt: {type: 'Date'},
|
||||||
|
updatedAt: {type: 'Date'},
|
||||||
|
objectId: {type: 'String'},
|
||||||
|
//Custom fields
|
||||||
|
aNumber: {type: 'Number'},
|
||||||
|
aString: {type: 'String'},
|
||||||
|
aBool: {type: 'Boolean'},
|
||||||
|
aDate: {type: 'Date'},
|
||||||
|
aObject: {type: 'Object'},
|
||||||
|
aArray: {type: 'Array'},
|
||||||
|
aGeoPoint: {type: 'GeoPoint'},
|
||||||
|
aFile: {type: 'File'}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'HasPointersAndRelations',
|
||||||
|
fields: {
|
||||||
|
//Default fields
|
||||||
|
ACL: {type: 'ACL'},
|
||||||
|
createdAt: {type: 'Date'},
|
||||||
|
updatedAt: {type: 'Date'},
|
||||||
|
objectId: {type: 'String'},
|
||||||
|
//Custom fields
|
||||||
|
aPointer: {
|
||||||
|
type: 'Pointer',
|
||||||
|
targetClass: 'HasAllPOD',
|
||||||
|
},
|
||||||
|
aRelation: {
|
||||||
|
type: 'Relation',
|
||||||
|
targetClass: 'HasAllPOD',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
expect(body).toEqual(expected);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
var transform = require('../transform');
|
var transform = require('../transform');
|
||||||
|
|
||||||
var dummyConfig = {
|
var dummySchema = {
|
||||||
schema: {
|
|
||||||
data: {},
|
data: {},
|
||||||
getExpectedType: function(className, key) {
|
getExpectedType: function(className, key) {
|
||||||
if (key == 'userPointer') {
|
if (key == 'userPointer') {
|
||||||
return '*_User';
|
return '*_User';
|
||||||
|
} else if (key == 'picture') {
|
||||||
|
return 'file';
|
||||||
|
} else if (key == 'location') {
|
||||||
|
return 'geopoint';
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ describe('transformCreate', () => {
|
|||||||
|
|
||||||
it('a basic number', (done) => {
|
it('a basic number', (done) => {
|
||||||
var input = {five: 5};
|
var input = {five: 5};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
jequal(input, output);
|
jequal(input, output);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -29,7 +31,7 @@ describe('transformCreate', () => {
|
|||||||
createdAt: "2015-10-06T21:24:50.332Z",
|
createdAt: "2015-10-06T21:24:50.332Z",
|
||||||
updatedAt: "2015-10-06T21:24:50.332Z"
|
updatedAt: "2015-10-06T21:24:50.332Z"
|
||||||
};
|
};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
expect(output._created_at instanceof Date).toBe(true);
|
expect(output._created_at instanceof Date).toBe(true);
|
||||||
expect(output._updated_at instanceof Date).toBe(true);
|
expect(output._updated_at instanceof Date).toBe(true);
|
||||||
done();
|
done();
|
||||||
@@ -41,21 +43,21 @@ describe('transformCreate', () => {
|
|||||||
objectId: 'myId',
|
objectId: 'myId',
|
||||||
className: 'Blah',
|
className: 'Blah',
|
||||||
};
|
};
|
||||||
var out = transform.transformCreate(dummyConfig, null, {pointers: [pointer]});
|
var out = transform.transformCreate(dummySchema, null, {pointers: [pointer]});
|
||||||
jequal([pointer], out.pointers);
|
jequal([pointer], out.pointers);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('a delete op', (done) => {
|
it('a delete op', (done) => {
|
||||||
var input = {deleteMe: {__op: 'Delete'}};
|
var input = {deleteMe: {__op: 'Delete'}};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
jequal(output, {});
|
jequal(output, {});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('basic ACL', (done) => {
|
it('basic ACL', (done) => {
|
||||||
var input = {ACL: {'0123': {'read': true, 'write': true}}};
|
var input = {ACL: {'0123': {'read': true, 'write': true}}};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
// This just checks that it doesn't crash, but it should check format.
|
// This just checks that it doesn't crash, but it should check format.
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -63,7 +65,7 @@ describe('transformCreate', () => {
|
|||||||
|
|
||||||
describe('transformWhere', () => {
|
describe('transformWhere', () => {
|
||||||
it('objectId', (done) => {
|
it('objectId', (done) => {
|
||||||
var out = transform.transformWhere(dummyConfig, null, {objectId: 'foo'});
|
var out = transform.transformWhere(dummySchema, null, {objectId: 'foo'});
|
||||||
expect(out._id).toEqual('foo');
|
expect(out._id).toEqual('foo');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -72,7 +74,7 @@ describe('transformWhere', () => {
|
|||||||
var input = {
|
var input = {
|
||||||
objectId: {'$in': ['one', 'two', 'three']},
|
objectId: {'$in': ['one', 'two', 'three']},
|
||||||
};
|
};
|
||||||
var output = transform.transformWhere(dummyConfig, null, input);
|
var output = transform.transformWhere(dummySchema, null, input);
|
||||||
jequal(input.objectId, output._id);
|
jequal(input.objectId, output._id);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -81,17 +83,53 @@ describe('transformWhere', () => {
|
|||||||
describe('untransformObject', () => {
|
describe('untransformObject', () => {
|
||||||
it('built-in timestamps', (done) => {
|
it('built-in timestamps', (done) => {
|
||||||
var input = {createdAt: new Date(), updatedAt: new Date()};
|
var input = {createdAt: new Date(), updatedAt: new Date()};
|
||||||
var output = transform.untransformObject(dummyConfig, null, input);
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
expect(typeof output.createdAt).toEqual('string');
|
expect(typeof output.createdAt).toEqual('string');
|
||||||
expect(typeof output.updatedAt).toEqual('string');
|
expect(typeof output.updatedAt).toEqual('string');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('pointer', (done) => {
|
||||||
|
var input = {_p_userPointer: '_User$123'};
|
||||||
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
|
expect(typeof output.userPointer).toEqual('object');
|
||||||
|
expect(output.userPointer).toEqual(
|
||||||
|
{__type: 'Pointer', className: '_User', objectId: '123'}
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('null pointer', (done) => {
|
||||||
|
var input = {_p_userPointer: null};
|
||||||
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
|
expect(output.userPointer).toBeUndefined();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('file', (done) => {
|
||||||
|
var input = {picture: 'pic.jpg'};
|
||||||
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
|
expect(typeof output.picture).toEqual('object');
|
||||||
|
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('geopoint', (done) => {
|
||||||
|
var input = {location: [180, -180]};
|
||||||
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
|
expect(typeof output.location).toEqual('object');
|
||||||
|
expect(output.location).toEqual(
|
||||||
|
{__type: 'GeoPoint', longitude: 180, latitude: -180}
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transformKey', () => {
|
describe('transformKey', () => {
|
||||||
it('throws out _password', (done) => {
|
it('throws out _password', (done) => {
|
||||||
try {
|
try {
|
||||||
transform.transformKey(dummyConfig, '_User', '_password');
|
transform.transformKey(dummySchema, '_User', '_password');
|
||||||
fail('should have thrown');
|
fail('should have thrown');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done();
|
done();
|
||||||
@@ -105,7 +143,7 @@ describe('transform schema key changes', () => {
|
|||||||
var input = {
|
var input = {
|
||||||
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
||||||
};
|
};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
expect(typeof output._p_somePointer).toEqual('string');
|
expect(typeof output._p_somePointer).toEqual('string');
|
||||||
expect(output._p_somePointer).toEqual('Micro$oft');
|
expect(output._p_somePointer).toEqual('Micro$oft');
|
||||||
done();
|
done();
|
||||||
@@ -115,7 +153,7 @@ describe('transform schema key changes', () => {
|
|||||||
var input = {
|
var input = {
|
||||||
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
||||||
};
|
};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
expect(typeof output._p_userPointer).toEqual('string');
|
expect(typeof output._p_userPointer).toEqual('string');
|
||||||
expect(output._p_userPointer).toEqual('_User$qwerty');
|
expect(output._p_userPointer).toEqual('_User$qwerty');
|
||||||
done();
|
done();
|
||||||
@@ -128,7 +166,7 @@ describe('transform schema key changes', () => {
|
|||||||
"Kevin": { "write": true }
|
"Kevin": { "write": true }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var output = transform.transformCreate(dummyConfig, null, input);
|
var output = transform.transformCreate(dummySchema, null, input);
|
||||||
expect(typeof output._rperm).toEqual('object');
|
expect(typeof output._rperm).toEqual('object');
|
||||||
expect(typeof output._wperm).toEqual('object');
|
expect(typeof output._wperm).toEqual('object');
|
||||||
expect(output.ACL).toBeUndefined();
|
expect(output.ACL).toBeUndefined();
|
||||||
@@ -142,7 +180,7 @@ describe('transform schema key changes', () => {
|
|||||||
_rperm: ["*"],
|
_rperm: ["*"],
|
||||||
_wperm: ["Kevin"]
|
_wperm: ["Kevin"]
|
||||||
};
|
};
|
||||||
var output = transform.untransformObject(dummyConfig, null, input);
|
var output = transform.untransformObject(dummySchema, null, input);
|
||||||
expect(typeof output.ACL).toEqual('object');
|
expect(typeof output.ACL).toEqual('object');
|
||||||
expect(output._rperm).toBeUndefined();
|
expect(output._rperm).toBeUndefined();
|
||||||
expect(output._wperm).toBeUndefined();
|
expect(output._wperm).toBeUndefined();
|
||||||
|
|||||||
10
transform.js
10
transform.js
@@ -48,7 +48,7 @@ function transformKeyValue(schema, className, restKey, restValue, options) {
|
|||||||
break;
|
break;
|
||||||
case 'expiresAt':
|
case 'expiresAt':
|
||||||
case '_expiresAt':
|
case '_expiresAt':
|
||||||
key = '_expiresAt';
|
key = 'expiresAt';
|
||||||
timeField = true;
|
timeField = true;
|
||||||
break;
|
break;
|
||||||
case '_rperm':
|
case '_rperm':
|
||||||
@@ -335,6 +335,7 @@ function transformAtom(atom, force, options) {
|
|||||||
return atom;
|
return atom;
|
||||||
|
|
||||||
case 'undefined':
|
case 'undefined':
|
||||||
|
return atom;
|
||||||
case 'symbol':
|
case 'symbol':
|
||||||
case 'function':
|
case 'function':
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||||
@@ -676,6 +677,9 @@ function untransformObject(schema, className, mongoObject) {
|
|||||||
console.log('Found a pointer in a non-pointer column, dropping it.', className, key);
|
console.log('Found a pointer in a non-pointer column, dropping it.', className, key);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (mongoObject[key] === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
var objData = mongoObject[key].split('$');
|
var objData = mongoObject[key].split('$');
|
||||||
var newClass = (expected ? expected.substring(1) : objData[0]);
|
var newClass = (expected ? expected.substring(1) : objData[0]);
|
||||||
if (objData[0] !== newClass) {
|
if (objData[0] !== newClass) {
|
||||||
@@ -689,9 +693,11 @@ function untransformObject(schema, className, mongoObject) {
|
|||||||
break;
|
break;
|
||||||
} else if (key[0] == '_' && key != '__type') {
|
} else if (key[0] == '_' && key != '__type') {
|
||||||
throw ('bad key in untransform: ' + key);
|
throw ('bad key in untransform: ' + key);
|
||||||
|
//} else if (mongoObject[key] === null) {
|
||||||
|
//break;
|
||||||
} else {
|
} else {
|
||||||
var expected = schema.getExpectedType(className, key);
|
var expected = schema.getExpectedType(className, key);
|
||||||
if (expected == 'file') {
|
if (expected == 'file' && mongoObject[key]) {
|
||||||
restObject[key] = {
|
restObject[key] = {
|
||||||
__type: 'File',
|
__type: 'File',
|
||||||
name: mongoObject[key]
|
name: mongoObject[key]
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ var getResponseObject = function(request, resolve, reject) {
|
|||||||
return resolve(response);
|
return resolve(response);
|
||||||
},
|
},
|
||||||
error: function(error) {
|
error: function(error) {
|
||||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, error);
|
var scriptError = new Parse.Error(Parse.Error.SCRIPT_FAILED, error);
|
||||||
|
return reject(scriptError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
34
users.js
34
users.js
@@ -5,18 +5,21 @@ var Parse = require('parse/node').Parse;
|
|||||||
var rack = require('hat').rack();
|
var rack = require('hat').rack();
|
||||||
|
|
||||||
var Auth = require('./Auth');
|
var Auth = require('./Auth');
|
||||||
var crypto = require('./crypto');
|
var passwordCrypto = require('./password');
|
||||||
var facebook = require('./facebook');
|
var facebook = require('./facebook');
|
||||||
var PromiseRouter = require('./PromiseRouter');
|
var PromiseRouter = require('./PromiseRouter');
|
||||||
var rest = require('./rest');
|
var rest = require('./rest');
|
||||||
var RestWrite = require('./RestWrite');
|
var RestWrite = require('./RestWrite');
|
||||||
|
var deepcopy = require('deepcopy');
|
||||||
|
|
||||||
var router = new PromiseRouter();
|
var router = new PromiseRouter();
|
||||||
|
|
||||||
// Returns a promise for a {status, response, location} object.
|
// Returns a promise for a {status, response, location} object.
|
||||||
function handleCreate(req) {
|
function handleCreate(req) {
|
||||||
|
var data = deepcopy(req.body);
|
||||||
|
data.installationId = req.info.installationId;
|
||||||
return rest.create(req.config, req.auth,
|
return rest.create(req.config, req.auth,
|
||||||
'_User', req.body);
|
'_User', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise for a {response} object.
|
// Returns a promise for a {response} object.
|
||||||
@@ -45,7 +48,7 @@ function handleLogIn(req) {
|
|||||||
'Invalid username/password.');
|
'Invalid username/password.');
|
||||||
}
|
}
|
||||||
user = results[0];
|
user = results[0];
|
||||||
return crypto.compare(req.body.password, user.password);
|
return passwordCrypto.compare(req.body.password, user.password);
|
||||||
}).then((correct) => {
|
}).then((correct) => {
|
||||||
if (!correct) {
|
if (!correct) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||||
@@ -70,9 +73,13 @@ function handleLogIn(req) {
|
|||||||
'authProvider': 'password'
|
'authProvider': 'password'
|
||||||
},
|
},
|
||||||
restricted: false,
|
restricted: false,
|
||||||
expiresAt: Parse._encode(expiresAt).iso,
|
expiresAt: Parse._encode(expiresAt)
|
||||||
installationId: req.info.installationId
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (req.info.installationId) {
|
||||||
|
sessionData.installationId = req.info.installationId
|
||||||
|
}
|
||||||
|
|
||||||
var create = new RestWrite(req.config, Auth.master(req.config),
|
var create = new RestWrite(req.config, Auth.master(req.config),
|
||||||
'_Session', null, sessionData);
|
'_Session', null, sessionData);
|
||||||
return create.execute();
|
return create.execute();
|
||||||
@@ -157,6 +164,22 @@ function handleDelete(req) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleLogOut(req) {
|
||||||
|
var success = {response: {}};
|
||||||
|
if (req.info && req.info.sessionToken) {
|
||||||
|
rest.find(req.config, Auth.master(req.config), '_Session',
|
||||||
|
{_session_token: req.info.sessionToken}
|
||||||
|
).then((records) => {
|
||||||
|
if (records.results && records.results.length) {
|
||||||
|
rest.del(req.config, Auth.master(req.config), '_Session',
|
||||||
|
records.results[0].id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(success);
|
||||||
|
}
|
||||||
|
|
||||||
function handleUpdate(req) {
|
function handleUpdate(req) {
|
||||||
return rest.update(req.config, req.auth, '_User',
|
return rest.update(req.config, req.auth, '_User',
|
||||||
req.params.objectId, req.body)
|
req.params.objectId, req.body)
|
||||||
@@ -172,6 +195,7 @@ function notImplementedYet(req) {
|
|||||||
|
|
||||||
router.route('POST', '/users', handleCreate);
|
router.route('POST', '/users', handleCreate);
|
||||||
router.route('GET', '/login', handleLogIn);
|
router.route('GET', '/login', handleLogIn);
|
||||||
|
router.route('POST', '/logout', handleLogOut);
|
||||||
router.route('GET', '/users/me', handleMe);
|
router.route('GET', '/users/me', handleMe);
|
||||||
router.route('GET', '/users/:objectId', handleGet);
|
router.route('GET', '/users/:objectId', handleGet);
|
||||||
router.route('PUT', '/users/:objectId', handleUpdate);
|
router.route('PUT', '/users/:objectId', handleUpdate);
|
||||||
|
|||||||
Reference in New Issue
Block a user