Update dependencies to enable Greenkeeper 🌴 (#3940)
* chore(package): update dependencies * docs(readme): add Greenkeeper badge * Fix indent issues with eslint 4.0 see http://eslint.org/docs/user-guide/migrating-to-4.0.0\#-the-indent-rule-is-more-strict
This commit is contained in:
committed by
Arthur Cinader
parent
16954c2f74
commit
e94991b368
@@ -129,11 +129,11 @@ OAuth.nonce = function(){
|
||||
}
|
||||
|
||||
OAuth.buildParameterString = function(obj){
|
||||
// Sort keys and encode values
|
||||
// Sort keys and encode values
|
||||
if (obj) {
|
||||
var keys = Object.keys(obj).sort();
|
||||
|
||||
// Map key=value, join them by &
|
||||
// Map key=value, join them by &
|
||||
return keys.map(function(key){
|
||||
return key + "=" + OAuth.encode(obj[key]);
|
||||
}).join("&");
|
||||
@@ -161,7 +161,7 @@ OAuth.signature = function(text, key){
|
||||
OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_token_secret){
|
||||
oauth_parameters = oauth_parameters || {};
|
||||
|
||||
// Set default values
|
||||
// Set default values
|
||||
if (!oauth_parameters.oauth_nonce) {
|
||||
oauth_parameters.oauth_nonce = OAuth.nonce();
|
||||
}
|
||||
@@ -178,12 +178,12 @@ OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_to
|
||||
if(!auth_token_secret){
|
||||
auth_token_secret = "";
|
||||
}
|
||||
// Force GET method if unset
|
||||
// Force GET method if unset
|
||||
if (!request.method) {
|
||||
request.method = "GET"
|
||||
}
|
||||
|
||||
// Collect all the parameters in one signatureParameters object
|
||||
// Collect all the parameters in one signatureParameters object
|
||||
var signatureParams = {};
|
||||
var parametersToMerge = [request.params, request.body, oauth_parameters];
|
||||
for(var i in parametersToMerge) {
|
||||
@@ -193,25 +193,25 @@ OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_to
|
||||
}
|
||||
}
|
||||
|
||||
// Create a string based on the parameters
|
||||
// Create a string based on the parameters
|
||||
var parameterString = OAuth.buildParameterString(signatureParams);
|
||||
|
||||
// Build the signature string
|
||||
// Build the signature string
|
||||
var url = "https://" + request.host + "" + request.path;
|
||||
|
||||
var signatureString = OAuth.buildSignatureString(request.method, url, parameterString);
|
||||
// Hash the signature string
|
||||
// Hash the signature string
|
||||
var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join("&");
|
||||
|
||||
var signature = OAuth.signature(signatureString, signatureKey);
|
||||
|
||||
// Set the signature in the params
|
||||
// Set the signature in the params
|
||||
oauth_parameters.oauth_signature = signature;
|
||||
if(!request.headers){
|
||||
request.headers = {};
|
||||
}
|
||||
|
||||
// Set the authorization header
|
||||
// Set the authorization header
|
||||
var authHeader = Object.keys(oauth_parameters).sort().map(function(key){
|
||||
var value = oauth_parameters[key];
|
||||
return key + '="' + value + '"';
|
||||
@@ -219,7 +219,7 @@ OAuth.signRequest = function(request, oauth_parameters, consumer_secret, auth_to
|
||||
|
||||
request.headers.Authorization = 'OAuth ' + authHeader;
|
||||
|
||||
// Set the content type header
|
||||
// Set the content type header
|
||||
request.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
return request;
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class MongoSchemaCollection {
|
||||
|
||||
_fetchAllSchemasFrom_SCHEMA() {
|
||||
return this._collection._rawFind({})
|
||||
.then(schemas => schemas.map(mongoSchemaToParseSchema));
|
||||
.then(schemas => schemas.map(mongoSchemaToParseSchema));
|
||||
}
|
||||
|
||||
_fechOneSchemaFrom_SCHEMA(name: string) {
|
||||
@@ -149,32 +149,32 @@ class MongoSchemaCollection {
|
||||
// TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint.
|
||||
addFieldIfNotExists(className: string, fieldName: string, type: string) {
|
||||
return this._fechOneSchemaFrom_SCHEMA(className)
|
||||
.then(schema => {
|
||||
.then(schema => {
|
||||
// The schema exists. Check for existing GeoPoints.
|
||||
if (type.type === 'GeoPoint') {
|
||||
if (type.type === 'GeoPoint') {
|
||||
// Make sure there are not other geopoint fields
|
||||
if (Object.keys(schema.fields).some(existingField => schema.fields[existingField].type === 'GeoPoint')) {
|
||||
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.');
|
||||
if (Object.keys(schema.fields).some(existingField => schema.fields[existingField].type === 'GeoPoint')) {
|
||||
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.');
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}, error => {
|
||||
return;
|
||||
}, error => {
|
||||
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
|
||||
// If some other error, reject with it.
|
||||
if (error === undefined) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(() => {
|
||||
if (error === undefined) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(() => {
|
||||
// We use $exists and $set to avoid overwriting the field type if it
|
||||
// already exists. (it could have added inbetween the last query and the update)
|
||||
return this.upsertSchema(
|
||||
className,
|
||||
{ [fieldName]: { '$exists': false } },
|
||||
{ '$set' : { [fieldName]: parseFieldTypeToMongoFieldType(type) } }
|
||||
);
|
||||
});
|
||||
return this.upsertSchema(
|
||||
className,
|
||||
{ [fieldName]: { '$exists': false } },
|
||||
{ '$set' : { [fieldName]: parseFieldTypeToMongoFieldType(type) } }
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,17 +22,17 @@ const MongoSchemaCollectionName = '_SCHEMA';
|
||||
|
||||
const storageAdapterAllCollections = mongoAdapter => {
|
||||
return mongoAdapter.connect()
|
||||
.then(() => mongoAdapter.database.collections())
|
||||
.then(collections => {
|
||||
return collections.filter(collection => {
|
||||
if (collection.namespace.match(/\.system\./)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: If you have one app with a collection prefix that happens to be a prefix of another
|
||||
// apps prefix, this will go very very badly. We should fix that somehow.
|
||||
return (collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0);
|
||||
.then(() => mongoAdapter.database.collections())
|
||||
.then(collections => {
|
||||
return collections.filter(collection => {
|
||||
if (collection.namespace.match(/\.system\./)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: If you have one app with a collection prefix that happens to be a prefix of another
|
||||
// apps prefix, this will go very very badly. We should fix that somehow.
|
||||
return (collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const convertParseSchemaToMongoSchema = ({...schema}) => {
|
||||
@@ -157,9 +157,9 @@ export class MongoStorageAdapter {
|
||||
|
||||
setClassLevelPermissions(className, CLPs) {
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, {
|
||||
$set: { _metadata: { class_permissions: CLPs } }
|
||||
}));
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, {
|
||||
$set: { _metadata: { class_permissions: CLPs } }
|
||||
}));
|
||||
}
|
||||
|
||||
createClass(className, schema) {
|
||||
@@ -167,43 +167,43 @@ export class MongoStorageAdapter {
|
||||
const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions);
|
||||
mongoObject._id = className;
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection._collection.insertOne(mongoObject))
|
||||
.then(result => MongoSchemaCollection._TESTmongoSchemaToParseSchema(result.ops[0]))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { //Mongo's duplicate key error
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(schemaCollection => schemaCollection._collection.insertOne(mongoObject))
|
||||
.then(result => MongoSchemaCollection._TESTmongoSchemaToParseSchema(result.ops[0]))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { //Mongo's duplicate key error
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addFieldIfNotExists(className, fieldName, type) {
|
||||
return this._schemaCollection()
|
||||
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type));
|
||||
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type));
|
||||
}
|
||||
|
||||
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
|
||||
// and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible.
|
||||
deleteClass(className) {
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.drop())
|
||||
.catch(error => {
|
||||
.then(collection => collection.drop())
|
||||
.catch(error => {
|
||||
// 'ns not found' means collection was already gone. Ignore deletion attempt.
|
||||
if (error.message == 'ns not found') {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
if (error.message == 'ns not found') {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
// We've dropped the collection, now remove the _SCHEMA document
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
|
||||
}
|
||||
|
||||
// Delete all data known to this adatper. Used for testing.
|
||||
deleteAllClasses() {
|
||||
return storageAdapterAllCollections(this)
|
||||
.then(collections => Promise.all(collections.map(collection => collection.drop())));
|
||||
.then(collections => Promise.all(collections.map(collection => collection.drop())));
|
||||
}
|
||||
|
||||
// Remove the column and all the data. For Relations, the _Join collection is handled
|
||||
@@ -245,9 +245,9 @@ export class MongoStorageAdapter {
|
||||
});
|
||||
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.updateMany({}, collectionUpdate))
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
|
||||
.then(collection => collection.updateMany({}, collectionUpdate))
|
||||
.then(() => this._schemaCollection())
|
||||
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
|
||||
}
|
||||
|
||||
// Return a promise for all schemas known to this adapter, in Parse format. In case the
|
||||
@@ -262,7 +262,7 @@ export class MongoStorageAdapter {
|
||||
// undefined as the reason.
|
||||
getClass(className) {
|
||||
return this._schemaCollection()
|
||||
.then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className))
|
||||
.then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className))
|
||||
}
|
||||
|
||||
// TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema,
|
||||
@@ -272,14 +272,14 @@ export class MongoStorageAdapter {
|
||||
schema = convertParseSchemaToMongoSchema(schema);
|
||||
const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.insertOne(mongoObject))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { // Duplicate value
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE,
|
||||
.then(collection => collection.insertOne(mongoObject))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) { // Duplicate value
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE,
|
||||
'A duplicate value for a field with unique values was provided');
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove all objects that match the given Parse Query.
|
||||
@@ -288,18 +288,18 @@ export class MongoStorageAdapter {
|
||||
deleteObjectsByQuery(className, schema, query) {
|
||||
schema = convertParseSchemaToMongoSchema(schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => {
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return collection.deleteMany(mongoWhere)
|
||||
})
|
||||
.then(({ result }) => {
|
||||
if (result.n === 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}, () => {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
|
||||
});
|
||||
.then(collection => {
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return collection.deleteMany(mongoWhere)
|
||||
})
|
||||
.then(({ result }) => {
|
||||
if (result.n === 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}, () => {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
|
||||
});
|
||||
}
|
||||
|
||||
// Apply the update to all objects that match the given Parse Query.
|
||||
@@ -308,7 +308,7 @@ export class MongoStorageAdapter {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.updateMany(mongoWhere, mongoUpdate));
|
||||
.then(collection => collection.updateMany(mongoWhere, mongoUpdate));
|
||||
}
|
||||
|
||||
// Atomically finds and updates an object based on query.
|
||||
@@ -318,8 +318,8 @@ export class MongoStorageAdapter {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }))
|
||||
.then(result => mongoObjectToParseObject(className, result.value, schema));
|
||||
.then(collection => collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }))
|
||||
.then(result => mongoObjectToParseObject(className, result.value, schema));
|
||||
}
|
||||
|
||||
// Hopefully we can get rid of this. It's only used for config and hooks.
|
||||
@@ -328,7 +328,7 @@ export class MongoStorageAdapter {
|
||||
const mongoUpdate = transformUpdate(className, update, schema);
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.upsertOne(mongoWhere, mongoUpdate));
|
||||
.then(collection => collection.upsertOne(mongoWhere, mongoUpdate));
|
||||
}
|
||||
|
||||
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
|
||||
@@ -341,14 +341,14 @@ export class MongoStorageAdapter {
|
||||
return memo;
|
||||
}, {});
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.find(mongoWhere, {
|
||||
skip,
|
||||
limit,
|
||||
sort: mongoSort,
|
||||
keys: mongoKeys,
|
||||
maxTimeMS: this._maxTimeMS,
|
||||
}))
|
||||
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
|
||||
.then(collection => collection.find(mongoWhere, {
|
||||
skip,
|
||||
limit,
|
||||
sort: mongoSort,
|
||||
keys: mongoKeys,
|
||||
maxTimeMS: this._maxTimeMS,
|
||||
}))
|
||||
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
|
||||
}
|
||||
|
||||
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
||||
@@ -364,14 +364,14 @@ export class MongoStorageAdapter {
|
||||
indexCreationRequest[fieldName] = 1;
|
||||
});
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
|
||||
.catch(error => {
|
||||
if (error.code === 11000) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Used in tests
|
||||
@@ -385,9 +385,9 @@ export class MongoStorageAdapter {
|
||||
count(className, schema, query) {
|
||||
schema = convertParseSchemaToMongoSchema(schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.count(transformWhere(className, query, schema), {
|
||||
maxTimeMS: this._maxTimeMS,
|
||||
}));
|
||||
.then(collection => collection.count(transformWhere(className, query, schema), {
|
||||
maxTimeMS: this._maxTimeMS,
|
||||
}));
|
||||
}
|
||||
|
||||
performInitialization() {
|
||||
@@ -396,7 +396,7 @@ export class MongoStorageAdapter {
|
||||
|
||||
createIndex(className, index) {
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection._mongoCollection.createIndex(index));
|
||||
.then(collection => collection._mongoCollection.createIndex(index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
|
||||
return {key, value};
|
||||
}
|
||||
|
||||
// Handle update operators
|
||||
// Handle update operators
|
||||
if (typeof restValue === 'object' && '__op' in restValue) {
|
||||
return {key, value: transformUpdateOperator(restValue, false)};
|
||||
}
|
||||
@@ -562,7 +562,7 @@ function transformConstraint(constraint, inArray) {
|
||||
const arr = constraint[key];
|
||||
if (!(arr instanceof Array)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'bad ' + key + ' value');
|
||||
'bad ' + key + ' value');
|
||||
}
|
||||
answer[key] = arr.map(transformInteriorAtom);
|
||||
break;
|
||||
|
||||
@@ -505,15 +505,15 @@ export class PostgresStorageAdapter {
|
||||
_ensureSchemaCollectionExists(conn) {
|
||||
conn = conn || this._client;
|
||||
return conn.none('CREATE TABLE IF NOT EXISTS "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )')
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError
|
||||
|| error.code === PostgresUniqueIndexViolationError
|
||||
|| error.code === PostgresDuplicateObjectError) {
|
||||
// Table already exists, must have been created by a different request. Ignore error.
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
classExists(name) {
|
||||
@@ -536,19 +536,19 @@ export class PostgresStorageAdapter {
|
||||
|
||||
return t.batch([q1, q2]);
|
||||
})
|
||||
.then(() => {
|
||||
return toParseSchema(schema)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (Array.isArray(err.data) && err.data.length > 1 && err.data[0].result.code === PostgresTransactionAbortedError) {
|
||||
err = err.data[1].result;
|
||||
}
|
||||
.then(() => {
|
||||
return toParseSchema(schema)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (Array.isArray(err.data) && err.data.length > 1 && err.data[0].result.code === PostgresTransactionAbortedError) {
|
||||
err = err.data[1].result;
|
||||
}
|
||||
|
||||
if (err.code === PostgresUniqueIndexViolationError && err.detail.includes(className)) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, `Class ${className} already exists.`)
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
if (err.code === PostgresUniqueIndexViolationError && err.detail.includes(className)) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, `Class ${className} already exists.`)
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
// Just create a table, do not insert in schema
|
||||
@@ -592,19 +592,19 @@ export class PostgresStorageAdapter {
|
||||
const qs = `CREATE TABLE IF NOT EXISTS $1:name (${patternsArray.join(',')})`;
|
||||
const values = [className, ...valuesArray];
|
||||
return this._ensureSchemaCollectionExists(conn)
|
||||
.then(() => conn.none(qs, values))
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError) {
|
||||
.then(() => conn.none(qs, values))
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError) {
|
||||
// Table already exists, must have been created by a different request. Ignore error.
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}).then(() => {
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}).then(() => {
|
||||
// Create the relation tables
|
||||
return Promise.all(relations.map((fieldName) => {
|
||||
return conn.none('CREATE TABLE IF NOT EXISTS $<joinTable:name> ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )', {joinTable: `_Join:${fieldName}:${className}`});
|
||||
}));
|
||||
});
|
||||
return Promise.all(relations.map((fieldName) => {
|
||||
return conn.none('CREATE TABLE IF NOT EXISTS $<joinTable:name> ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )', {joinTable: `_Join:${fieldName}:${className}`});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
addFieldIfNotExists(className, fieldName, type) {
|
||||
@@ -618,16 +618,16 @@ export class PostgresStorageAdapter {
|
||||
fieldName,
|
||||
postgresType: parseTypeToPostgresType(type)
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
return this.createClass(className, {fields: {[fieldName]: type}})
|
||||
} else if (error.code === PostgresDuplicateColumnError) {
|
||||
.catch(error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
return this.createClass(className, {fields: {[fieldName]: type}})
|
||||
} else if (error.code === PostgresDuplicateColumnError) {
|
||||
// Column already exists, created by other request. Carry on to
|
||||
// See if it's the right type.
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
promise = t.none('CREATE TABLE IF NOT EXISTS $<joinTable:name> ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )', {joinTable: `_Join:${fieldName}:${className}`})
|
||||
}
|
||||
@@ -663,22 +663,22 @@ export class PostgresStorageAdapter {
|
||||
const now = new Date().getTime();
|
||||
debug('deleteAllClasses');
|
||||
return this._client.any('SELECT * FROM "_SCHEMA"')
|
||||
.then(results => {
|
||||
const joins = results.reduce((list, schema) => {
|
||||
return list.concat(joinTablesForSchema(schema.schema));
|
||||
}, []);
|
||||
const classes = ['_SCHEMA','_PushStatus','_JobStatus','_JobSchedule','_Hooks','_GlobalConfig', ...results.map(result => result.className), ...joins];
|
||||
return this._client.tx(t=>t.batch(classes.map(className=>t.none('DROP TABLE IF EXISTS $<className:name>', { className }))));
|
||||
}, error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
.then(results => {
|
||||
const joins = results.reduce((list, schema) => {
|
||||
return list.concat(joinTablesForSchema(schema.schema));
|
||||
}, []);
|
||||
const classes = ['_SCHEMA','_PushStatus','_JobStatus','_JobSchedule','_Hooks','_GlobalConfig', ...results.map(result => result.className), ...joins];
|
||||
return this._client.tx(t=>t.batch(classes.map(className=>t.none('DROP TABLE IF EXISTS $<className:name>', { className }))));
|
||||
}, error => {
|
||||
if (error.code === PostgresRelationDoesNotExistError) {
|
||||
// No _SCHEMA collection. Don't delete anything.
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}).then(() => {
|
||||
debug(`deleteAllClasses done in ${new Date().getTime() - now}`);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}).then(() => {
|
||||
debug(`deleteAllClasses done in ${new Date().getTime() - now}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the column and all the data. For Relations, the _Join collection is handled
|
||||
@@ -697,34 +697,34 @@ export class PostgresStorageAdapter {
|
||||
deleteFields(className, schema, fieldNames) {
|
||||
debug('deleteFields', className, fieldNames);
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
fieldNames = fieldNames.reduce((list, fieldName) => {
|
||||
const field = schema.fields[fieldName]
|
||||
if (field.type !== 'Relation') {
|
||||
list.push(fieldName);
|
||||
}
|
||||
delete schema.fields[fieldName];
|
||||
return list;
|
||||
}, []);
|
||||
.then(() => {
|
||||
fieldNames = fieldNames.reduce((list, fieldName) => {
|
||||
const field = schema.fields[fieldName]
|
||||
if (field.type !== 'Relation') {
|
||||
list.push(fieldName);
|
||||
}
|
||||
delete schema.fields[fieldName];
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
const values = [className, ...fieldNames];
|
||||
const columns = fieldNames.map((name, idx) => {
|
||||
return `$${idx + 2}:name`;
|
||||
}).join(', DROP COLUMN');
|
||||
const values = [className, ...fieldNames];
|
||||
const columns = fieldNames.map((name, idx) => {
|
||||
return `$${idx + 2}:name`;
|
||||
}).join(', DROP COLUMN');
|
||||
|
||||
const doBatch = (t) => {
|
||||
const batch = [
|
||||
t.none('UPDATE "_SCHEMA" SET "schema"=$<schema> WHERE "className"=$<className>', {schema, className})
|
||||
];
|
||||
if (values.length > 1) {
|
||||
batch.push(t.none(`ALTER TABLE $1:name DROP COLUMN ${columns}`, values));
|
||||
const doBatch = (t) => {
|
||||
const batch = [
|
||||
t.none('UPDATE "_SCHEMA" SET "schema"=$<schema> WHERE "className"=$<className>', {schema, className})
|
||||
];
|
||||
if (values.length > 1) {
|
||||
batch.push(t.none(`ALTER TABLE $1:name DROP COLUMN ${columns}`, values));
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
return this._client.tx((t) => {
|
||||
return t.batch(doBatch(t));
|
||||
return this._client.tx((t) => {
|
||||
return t.batch(doBatch(t));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Return a promise for all schemas known to this adapter, in Parse format. In case the
|
||||
@@ -732,8 +732,8 @@ export class PostgresStorageAdapter {
|
||||
// rejection reason are TBD.
|
||||
getAllClasses() {
|
||||
return this._ensureSchemaCollectionExists()
|
||||
.then(() => this._client.map('SELECT * FROM "_SCHEMA"', null, row => ({ className: row.className, ...row.schema })))
|
||||
.then(res => res.map(toParseSchema))
|
||||
.then(() => this._client.map('SELECT * FROM "_SCHEMA"', null, row => ({ className: row.className, ...row.schema })))
|
||||
.then(res => res.map(toParseSchema))
|
||||
}
|
||||
|
||||
// Return a promise for the schema with the given name, in Parse format. If
|
||||
@@ -742,13 +742,13 @@ export class PostgresStorageAdapter {
|
||||
getClass(className) {
|
||||
debug('getClass', className);
|
||||
return this._client.any('SELECT * FROM "_SCHEMA" WHERE "className"=$<className>', { className })
|
||||
.then(result => {
|
||||
if (result.length === 1) {
|
||||
return result[0].schema;
|
||||
} else {
|
||||
throw undefined;
|
||||
}
|
||||
}).then(toParseSchema);
|
||||
.then(result => {
|
||||
if (result.length === 1) {
|
||||
return result[0].schema;
|
||||
} else {
|
||||
throw undefined;
|
||||
}
|
||||
}).then(toParseSchema);
|
||||
}
|
||||
|
||||
// TODO: remove the mongo format dependency in the return value
|
||||
@@ -830,7 +830,7 @@ export class PostgresStorageAdapter {
|
||||
valuesArray.push(object[fieldName].name);
|
||||
break;
|
||||
case 'GeoPoint':
|
||||
// pop the point and process later
|
||||
// pop the point and process later
|
||||
geoPoints[fieldName] = object[fieldName];
|
||||
columnsArray.pop();
|
||||
break;
|
||||
@@ -864,14 +864,14 @@ export class PostgresStorageAdapter {
|
||||
const values = [className, ...columnsArray, ...valuesArray]
|
||||
debug(qs, values);
|
||||
return this._client.none(qs, values)
|
||||
.then(() => ({ ops: [object] }))
|
||||
.catch(error => {
|
||||
if (error.code === PostgresUniqueIndexViolationError) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(() => ({ ops: [object] }))
|
||||
.catch(error => {
|
||||
if (error.code === PostgresUniqueIndexViolationError) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Remove all objects that match the given Parse Query.
|
||||
@@ -889,13 +889,13 @@ export class PostgresStorageAdapter {
|
||||
const qs = `WITH deleted AS (DELETE FROM $1:name WHERE ${where.pattern} RETURNING *) SELECT count(*) FROM deleted`;
|
||||
debug(qs, values);
|
||||
return this._client.one(qs, values , a => +a.count)
|
||||
.then(count => {
|
||||
if (count === 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
} else {
|
||||
return count;
|
||||
}
|
||||
});
|
||||
.then(count => {
|
||||
if (count === 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
} else {
|
||||
return count;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Return value not currently well specified.
|
||||
findOneAndUpdate(className, schema, query, update) {
|
||||
@@ -1145,72 +1145,72 @@ export class PostgresStorageAdapter {
|
||||
const qs = `SELECT ${columns} FROM $1:name ${wherePattern} ${sortPattern} ${limitPattern} ${skipPattern}`;
|
||||
debug(qs, values);
|
||||
return this._client.any(qs, values)
|
||||
.catch((err) => {
|
||||
.catch((err) => {
|
||||
// Query on non existing table, don't crash
|
||||
if (err.code === PostgresRelationDoesNotExistError) {
|
||||
return [];
|
||||
}
|
||||
return Promise.reject(err);
|
||||
})
|
||||
.then(results => results.map(object => {
|
||||
Object.keys(schema.fields).forEach(fieldName => {
|
||||
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
||||
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
||||
if (err.code === PostgresRelationDoesNotExistError) {
|
||||
return [];
|
||||
}
|
||||
if (schema.fields[fieldName].type === 'Relation') {
|
||||
object[fieldName] = {
|
||||
__type: "Relation",
|
||||
className: schema.fields[fieldName].targetClass
|
||||
return Promise.reject(err);
|
||||
})
|
||||
.then(results => results.map(object => {
|
||||
Object.keys(schema.fields).forEach(fieldName => {
|
||||
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
||||
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
||||
}
|
||||
}
|
||||
if (object[fieldName] && schema.fields[fieldName].type === 'GeoPoint') {
|
||||
object[fieldName] = {
|
||||
__type: "GeoPoint",
|
||||
latitude: object[fieldName].y,
|
||||
longitude: object[fieldName].x
|
||||
if (schema.fields[fieldName].type === 'Relation') {
|
||||
object[fieldName] = {
|
||||
__type: "Relation",
|
||||
className: schema.fields[fieldName].targetClass
|
||||
}
|
||||
}
|
||||
}
|
||||
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
||||
object[fieldName] = {
|
||||
__type: 'File',
|
||||
name: object[fieldName]
|
||||
if (object[fieldName] && schema.fields[fieldName].type === 'GeoPoint') {
|
||||
object[fieldName] = {
|
||||
__type: "GeoPoint",
|
||||
latitude: object[fieldName].y,
|
||||
longitude: object[fieldName].x
|
||||
}
|
||||
}
|
||||
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
||||
object[fieldName] = {
|
||||
__type: 'File',
|
||||
name: object[fieldName]
|
||||
}
|
||||
}
|
||||
});
|
||||
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
|
||||
if (object.createdAt) {
|
||||
object.createdAt = object.createdAt.toISOString();
|
||||
}
|
||||
if (object.updatedAt) {
|
||||
object.updatedAt = object.updatedAt.toISOString();
|
||||
}
|
||||
if (object.expiresAt) {
|
||||
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
|
||||
}
|
||||
if (object._email_verify_token_expires_at) {
|
||||
object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() };
|
||||
}
|
||||
if (object._account_lockout_expires_at) {
|
||||
object._account_lockout_expires_at = { __type: 'Date', iso: object._account_lockout_expires_at.toISOString() };
|
||||
}
|
||||
if (object._perishable_token_expires_at) {
|
||||
object._perishable_token_expires_at = { __type: 'Date', iso: object._perishable_token_expires_at.toISOString() };
|
||||
}
|
||||
if (object._password_changed_at) {
|
||||
object._password_changed_at = { __type: 'Date', iso: object._password_changed_at.toISOString() };
|
||||
}
|
||||
});
|
||||
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
|
||||
if (object.createdAt) {
|
||||
object.createdAt = object.createdAt.toISOString();
|
||||
}
|
||||
if (object.updatedAt) {
|
||||
object.updatedAt = object.updatedAt.toISOString();
|
||||
}
|
||||
if (object.expiresAt) {
|
||||
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
|
||||
}
|
||||
if (object._email_verify_token_expires_at) {
|
||||
object._email_verify_token_expires_at = { __type: 'Date', iso: object._email_verify_token_expires_at.toISOString() };
|
||||
}
|
||||
if (object._account_lockout_expires_at) {
|
||||
object._account_lockout_expires_at = { __type: 'Date', iso: object._account_lockout_expires_at.toISOString() };
|
||||
}
|
||||
if (object._perishable_token_expires_at) {
|
||||
object._perishable_token_expires_at = { __type: 'Date', iso: object._perishable_token_expires_at.toISOString() };
|
||||
}
|
||||
if (object._password_changed_at) {
|
||||
object._password_changed_at = { __type: 'Date', iso: object._password_changed_at.toISOString() };
|
||||
}
|
||||
|
||||
for (const fieldName in object) {
|
||||
if (object[fieldName] === null) {
|
||||
delete object[fieldName];
|
||||
for (const fieldName in object) {
|
||||
if (object[fieldName] === null) {
|
||||
delete object[fieldName];
|
||||
}
|
||||
if (object[fieldName] instanceof Date) {
|
||||
object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() };
|
||||
}
|
||||
}
|
||||
if (object[fieldName] instanceof Date) {
|
||||
object[fieldName] = { __type: 'Date', iso: object[fieldName].toISOString() };
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}));
|
||||
return object;
|
||||
}));
|
||||
}
|
||||
|
||||
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
||||
@@ -1225,16 +1225,16 @@ export class PostgresStorageAdapter {
|
||||
const constraintPatterns = fieldNames.map((fieldName, index) => `$${index + 3}:name`);
|
||||
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)) {
|
||||
.catch(error => {
|
||||
if (error.code === PostgresDuplicateRelationError && error.message.includes(constraintName)) {
|
||||
// Index already exists. Ignore error.
|
||||
} else if (error.code === PostgresUniqueIndexViolationError && error.message.includes(constraintName)) {
|
||||
} else if (error.code === PostgresUniqueIndexViolationError && error.message.includes(constraintName)) {
|
||||
// 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 {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Executes a count.
|
||||
@@ -1352,11 +1352,11 @@ function literalizeRegexPart(s) {
|
||||
// remove all instances of \Q and \E from the remaining text & escape single quotes
|
||||
return (
|
||||
s.replace(/([^\\])(\\E)/, '$1')
|
||||
.replace(/([^\\])(\\Q)/, '$1')
|
||||
.replace(/^\\E/, '')
|
||||
.replace(/^\\Q/, '')
|
||||
.replace(/([^'])'/, `$1''`)
|
||||
.replace(/^'([^'])/, `''$1`)
|
||||
.replace(/([^\\])(\\Q)/, '$1')
|
||||
.replace(/^\\E/, '')
|
||||
.replace(/^\\Q/, '')
|
||||
.replace(/([^'])'/, `$1''`)
|
||||
.replace(/^'([^'])/, `''$1`)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ DatabaseController.prototype.collectionExists = function(className) {
|
||||
|
||||
DatabaseController.prototype.purgeCollection = function(className) {
|
||||
return this.loadSchema()
|
||||
.then(schemaController => schemaController.getOneSchema(className))
|
||||
.then(schema => this.adapter.deleteObjectsByQuery(className, schema, {}));
|
||||
.then(schemaController => schemaController.getOneSchema(className))
|
||||
.then(schema => this.adapter.deleteObjectsByQuery(className, schema, {}));
|
||||
};
|
||||
|
||||
DatabaseController.prototype.validateClassName = function(className) {
|
||||
@@ -148,7 +148,7 @@ DatabaseController.prototype.loadSchema = function(options = {clearCache: false}
|
||||
if (!this.schemaPromise) {
|
||||
this.schemaPromise = SchemaController.load(this.adapter, this.schemaCache, options);
|
||||
this.schemaPromise.then(() => delete this.schemaPromise,
|
||||
() => delete this.schemaPromise);
|
||||
() => delete this.schemaPromise);
|
||||
}
|
||||
return this.schemaPromise;
|
||||
};
|
||||
@@ -243,69 +243,69 @@ DatabaseController.prototype.update = function(className, query, update, {
|
||||
var isMaster = acl === undefined;
|
||||
var aclGroup = acl || [];
|
||||
return this.loadSchema()
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
|
||||
.then(() => {
|
||||
relationUpdates = this.collectRelationUpdates(className, originalQuery.objectId, update);
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
|
||||
}
|
||||
if (!query) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
return schemaController.getOneSchema(className, true)
|
||||
.catch(error => {
|
||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
||||
// will likely need revisiting.
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(schema => {
|
||||
Object.keys(update).forEach(fieldName => {
|
||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
|
||||
.then(() => {
|
||||
relationUpdates = this.collectRelationUpdates(className, originalQuery.objectId, update);
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
|
||||
}
|
||||
fieldName = fieldName.split('.')[0];
|
||||
if (!SchemaController.fieldNameIsValid(fieldName) && !isSpecialUpdateKey(fieldName)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
|
||||
if (!query) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
return schemaController.getOneSchema(className, true)
|
||||
.catch(error => {
|
||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
||||
// will likely need revisiting.
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(schema => {
|
||||
Object.keys(update).forEach(fieldName => {
|
||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
|
||||
}
|
||||
fieldName = fieldName.split('.')[0];
|
||||
if (!SchemaController.fieldNameIsValid(fieldName) && !isSpecialUpdateKey(fieldName)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
|
||||
}
|
||||
});
|
||||
for (const updateOperation in update) {
|
||||
if (Object.keys(updateOperation).some(innerKey => innerKey.includes('$') || innerKey.includes('.'))) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||
}
|
||||
}
|
||||
update = transformObjectACL(update);
|
||||
transformAuthData(className, update, schema);
|
||||
if (many) {
|
||||
return this.adapter.updateObjectsByQuery(className, schema, query, update);
|
||||
} else if (upsert) {
|
||||
return this.adapter.upsertOneObject(className, schema, query, update);
|
||||
} else {
|
||||
return this.adapter.findOneAndUpdate(className, schema, query, update)
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(result => {
|
||||
if (!result) {
|
||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
|
||||
}
|
||||
return this.handleRelationUpdates(className, originalQuery.objectId, update, relationUpdates).then(() => {
|
||||
return result;
|
||||
});
|
||||
}).then((result) => {
|
||||
if (skipSanitization) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
return sanitizeDatabaseResult(originalUpdate, result);
|
||||
});
|
||||
for (const updateOperation in update) {
|
||||
if (Object.keys(updateOperation).some(innerKey => innerKey.includes('$') || innerKey.includes('.'))) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
|
||||
}
|
||||
}
|
||||
update = transformObjectACL(update);
|
||||
transformAuthData(className, update, schema);
|
||||
if (many) {
|
||||
return this.adapter.updateObjectsByQuery(className, schema, query, update);
|
||||
} else if (upsert) {
|
||||
return this.adapter.upsertOneObject(className, schema, query, update);
|
||||
} else {
|
||||
return this.adapter.findOneAndUpdate(className, schema, query, update)
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(result => {
|
||||
if (!result) {
|
||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
|
||||
}
|
||||
return this.handleRelationUpdates(className, originalQuery.objectId, update, relationUpdates).then(() => {
|
||||
return result;
|
||||
});
|
||||
}).then((result) => {
|
||||
if (skipSanitization) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
return sanitizeDatabaseResult(originalUpdate, result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function sanitizeDatabaseResult(originalObject, result) {
|
||||
@@ -375,16 +375,16 @@ DatabaseController.prototype.handleRelationUpdates = function(className, objectI
|
||||
if (op.__op == 'AddRelation') {
|
||||
for (const object of op.objects) {
|
||||
pending.push(this.addRelation(key, className,
|
||||
objectId,
|
||||
object.objectId));
|
||||
objectId,
|
||||
object.objectId));
|
||||
}
|
||||
}
|
||||
|
||||
if (op.__op == 'RemoveRelation') {
|
||||
for (const object of op.objects) {
|
||||
pending.push(this.removeRelation(key, className,
|
||||
objectId,
|
||||
object.objectId));
|
||||
objectId,
|
||||
object.objectId));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -412,13 +412,13 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI
|
||||
owningId: fromId
|
||||
};
|
||||
return this.adapter.deleteObjectsByQuery(`_Join:${key}:${fromClassName}`, relationSchema, doc)
|
||||
.catch(error => {
|
||||
.catch(error => {
|
||||
// We don't care if they try to delete a non-existent relation.
|
||||
if (error.code == Parse.Error.OBJECT_NOT_FOUND) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
if (error.code == Parse.Error.OBJECT_NOT_FOUND) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
// Removes objects matches this query from the database.
|
||||
@@ -433,39 +433,39 @@ DatabaseController.prototype.destroy = function(className, query, { acl } = {})
|
||||
const aclGroup = acl || [];
|
||||
|
||||
return this.loadSchema()
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete'))
|
||||
.then(() => {
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, 'delete', query, aclGroup);
|
||||
if (!query) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
}
|
||||
// delete by query
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
return schemaController.getOneSchema(className)
|
||||
.catch(error => {
|
||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
||||
// will likely need revisiting.
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, parseFormatSchema, query))
|
||||
.catch(error => {
|
||||
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
||||
if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete'))
|
||||
.then(() => {
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, 'delete', query, aclGroup);
|
||||
if (!query) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
}
|
||||
}
|
||||
// delete by query
|
||||
if (acl) {
|
||||
query = addWriteACL(query, acl);
|
||||
}
|
||||
validateQuery(query);
|
||||
return schemaController.getOneSchema(className)
|
||||
.catch(error => {
|
||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
||||
// will likely need revisiting.
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, parseFormatSchema, query))
|
||||
.catch(error => {
|
||||
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
||||
if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const flattenUpdateOperatorsForCreate = object => {
|
||||
@@ -538,23 +538,23 @@ DatabaseController.prototype.create = function(className, object, { acl } = {})
|
||||
var aclGroup = acl || [];
|
||||
const relationUpdates = this.collectRelationUpdates(className, null, object);
|
||||
return this.validateClassName(className)
|
||||
.then(() => this.loadSchema())
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
||||
.then(() => schemaController.enforceClassExists(className))
|
||||
.then(() => schemaController.reloadData())
|
||||
.then(() => schemaController.getOneSchema(className, true))
|
||||
.then(schema => {
|
||||
transformAuthData(className, object, schema);
|
||||
flattenUpdateOperatorsForCreate(object);
|
||||
return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
|
||||
.then(() => this.loadSchema())
|
||||
.then(schemaController => {
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
||||
.then(() => schemaController.enforceClassExists(className))
|
||||
.then(() => schemaController.reloadData())
|
||||
.then(() => schemaController.getOneSchema(className, true))
|
||||
.then(schema => {
|
||||
transformAuthData(className, object, schema);
|
||||
flattenUpdateOperatorsForCreate(object);
|
||||
return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
|
||||
})
|
||||
.then(result => {
|
||||
return this.handleRelationUpdates(className, null, object, relationUpdates).then(() => {
|
||||
return sanitizeDatabaseResult(originalObject, result.ops[0])
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(result => {
|
||||
return this.handleRelationUpdates(className, null, object, relationUpdates).then(() => {
|
||||
return sanitizeDatabaseResult(originalObject, result.ops[0])
|
||||
});
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
|
||||
@@ -587,14 +587,14 @@ DatabaseController.prototype.deleteEverything = function() {
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
||||
return this.adapter.find(joinTableName(className, key), relationSchema, { owningId }, {})
|
||||
.then(results => results.map(result => result.relatedId));
|
||||
.then(results => results.map(result => result.relatedId));
|
||||
};
|
||||
|
||||
// Returns a promise for a list of owning ids given some related ids.
|
||||
// className here is the owning className.
|
||||
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
||||
return this.adapter.find(joinTableName(className, key), relationSchema, { relatedId: { '$in': relatedIds } }, {})
|
||||
.then(results => results.map(result => result.owningId));
|
||||
.then(results => results.map(result => result.owningId));
|
||||
};
|
||||
|
||||
// Modifies query so that it no longer has $in on relation fields, or
|
||||
@@ -691,10 +691,10 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
|
||||
relatedTo.object.className,
|
||||
relatedTo.key,
|
||||
relatedTo.object.objectId).then((ids) => {
|
||||
delete query['$relatedTo'];
|
||||
this.addInObjectIdsIds(ids, query);
|
||||
return this.reduceRelationKeys(className, query);
|
||||
});
|
||||
delete query['$relatedTo'];
|
||||
this.addInObjectIdsIds(ids, query);
|
||||
return this.reduceRelationKeys(className, query);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -777,80 +777,80 @@ DatabaseController.prototype.find = function(className, query, {
|
||||
|
||||
let classExists = true;
|
||||
return this.loadSchema()
|
||||
.then(schemaController => {
|
||||
.then(schemaController => {
|
||||
//Allow volatile classes if querying with Master (for _PushStatus)
|
||||
//TODO: Move volatile classes concept into mongo adatper, postgres adapter shouldn't care
|
||||
//that api.parse.com breaks when _PushStatus exists in mongo.
|
||||
return schemaController.getOneSchema(className, isMaster)
|
||||
.catch(error => {
|
||||
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
||||
// For now, pretend the class exists but has no objects,
|
||||
if (error === undefined) {
|
||||
classExists = false;
|
||||
return { fields: {} };
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(schema => {
|
||||
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
||||
// so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to
|
||||
// use the one that appears first in the sort list.
|
||||
if (sort._created_at) {
|
||||
sort.createdAt = sort._created_at;
|
||||
delete sort._created_at;
|
||||
}
|
||||
if (sort._updated_at) {
|
||||
sort.updatedAt = sort._updated_at;
|
||||
delete sort._updated_at;
|
||||
}
|
||||
Object.keys(sort).forEach(fieldName => {
|
||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
|
||||
}
|
||||
if (!SchemaController.fieldNameIsValid(fieldName)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
||||
}
|
||||
});
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
|
||||
.then(() => this.reduceRelationKeys(className, query))
|
||||
.then(() => this.reduceInRelation(className, query, schemaController))
|
||||
.then(() => {
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
|
||||
}
|
||||
if (!query) {
|
||||
if (op == 'get') {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
} else {
|
||||
return [];
|
||||
return schemaController.getOneSchema(className, isMaster)
|
||||
.catch(error => {
|
||||
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
||||
// For now, pretend the class exists but has no objects,
|
||||
if (error === undefined) {
|
||||
classExists = false;
|
||||
return { fields: {} };
|
||||
}
|
||||
}
|
||||
if (!isMaster) {
|
||||
query = addReadACL(query, aclGroup);
|
||||
}
|
||||
validateQuery(query);
|
||||
if (count) {
|
||||
if (!classExists) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.adapter.count(className, schema, query);
|
||||
throw error;
|
||||
})
|
||||
.then(schema => {
|
||||
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
||||
// so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to
|
||||
// use the one that appears first in the sort list.
|
||||
if (sort._created_at) {
|
||||
sort.createdAt = sort._created_at;
|
||||
delete sort._created_at;
|
||||
}
|
||||
} else {
|
||||
if (!classExists) {
|
||||
return [];
|
||||
} else {
|
||||
return this.adapter.find(className, schema, query, { skip, limit, sort, keys })
|
||||
.then(objects => objects.map(object => {
|
||||
object = untransformObjectACL(object);
|
||||
return filterSensitiveData(isMaster, aclGroup, className, object)
|
||||
})).catch((error) => {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, error);
|
||||
if (sort._updated_at) {
|
||||
sort.updatedAt = sort._updated_at;
|
||||
delete sort._updated_at;
|
||||
}
|
||||
Object.keys(sort).forEach(fieldName => {
|
||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
|
||||
}
|
||||
if (!SchemaController.fieldNameIsValid(fieldName)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
||||
}
|
||||
});
|
||||
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
|
||||
.then(() => this.reduceRelationKeys(className, query))
|
||||
.then(() => this.reduceInRelation(className, query, schemaController))
|
||||
.then(() => {
|
||||
if (!isMaster) {
|
||||
query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
|
||||
}
|
||||
if (!query) {
|
||||
if (op == 'get') {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (!isMaster) {
|
||||
query = addReadACL(query, aclGroup);
|
||||
}
|
||||
validateQuery(query);
|
||||
if (count) {
|
||||
if (!classExists) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.adapter.count(className, schema, query);
|
||||
}
|
||||
} else {
|
||||
if (!classExists) {
|
||||
return [];
|
||||
} else {
|
||||
return this.adapter.find(className, schema, query, { skip, limit, sort, keys })
|
||||
.then(objects => objects.map(object => {
|
||||
object = untransformObjectACL(object);
|
||||
return filterSensitiveData(isMaster, aclGroup, className, object)
|
||||
})).catch((error) => {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Transforms a Database format ACL to a REST API format ACL
|
||||
@@ -879,32 +879,32 @@ const untransformObjectACL = ({_rperm, _wperm, ...output}) => {
|
||||
|
||||
DatabaseController.prototype.deleteSchema = function(className) {
|
||||
return this.loadSchema(true)
|
||||
.then(schemaController => schemaController.getOneSchema(className, true))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(schema => {
|
||||
return this.collectionExists(className)
|
||||
.then(() => this.adapter.count(className, { fields: {} }))
|
||||
.then(count => {
|
||||
if (count > 0) {
|
||||
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
||||
}
|
||||
return this.adapter.deleteClass(className);
|
||||
})
|
||||
.then(wasParseCollection => {
|
||||
if (wasParseCollection) {
|
||||
const relationFieldNames = Object.keys(schema.fields).filter(fieldName => schema.fields[fieldName].type === 'Relation');
|
||||
return Promise.all(relationFieldNames.map(name => this.adapter.deleteClass(joinTableName(className, name))));
|
||||
.then(schemaController => schemaController.getOneSchema(className, true))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
return { fields: {} };
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
.then(schema => {
|
||||
return this.collectionExists(className)
|
||||
.then(() => this.adapter.count(className, { fields: {} }))
|
||||
.then(count => {
|
||||
if (count > 0) {
|
||||
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
||||
}
|
||||
return this.adapter.deleteClass(className);
|
||||
})
|
||||
.then(wasParseCollection => {
|
||||
if (wasParseCollection) {
|
||||
const relationFieldNames = Object.keys(schema.fields).filter(fieldName => schema.fields[fieldName].type === 'Relation');
|
||||
return Promise.all(relationFieldNames.map(name => this.adapter.deleteClass(joinTableName(className, name))));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
DatabaseController.prototype.addPointerPermissions = function(schema, className, operation, query, aclGroup = []) {
|
||||
|
||||
@@ -10,7 +10,7 @@ export class PushController {
|
||||
sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}) {
|
||||
if (!config.hasPushSupport) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Missing push configuration');
|
||||
'Missing push configuration');
|
||||
}
|
||||
// Replace the expiration_time and push_time with a valid Unix epoch milliseconds time
|
||||
body.expiration_time = PushController.getExpirationTime(body);
|
||||
@@ -82,12 +82,12 @@ export class PushController {
|
||||
expirationTime = new Date(expirationTimeParam);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
}
|
||||
// Check expirationTime is valid or not, if it is not valid, expirationTime is NaN
|
||||
if (!isFinite(expirationTime)) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
}
|
||||
return expirationTime.valueOf();
|
||||
}
|
||||
@@ -110,12 +110,12 @@ export class PushController {
|
||||
pushTime = new Date(pushTimeParam);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.');
|
||||
body['push_time'] + ' is not valid time.');
|
||||
}
|
||||
// Check pushTime is valid or not, if it is not valid, pushTime is NaN
|
||||
if (!isFinite(pushTime)) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.');
|
||||
body['push_time'] + ' is not valid time.');
|
||||
}
|
||||
return pushTime;
|
||||
}
|
||||
|
||||
@@ -352,29 +352,29 @@ export default class SchemaController {
|
||||
this.reloadDataPromise = promise.then(() => {
|
||||
return this.getAllClasses(options);
|
||||
})
|
||||
.then(allSchemas => {
|
||||
const data = {};
|
||||
const perms = {};
|
||||
allSchemas.forEach(schema => {
|
||||
data[schema.className] = injectDefaultSchema(schema).fields;
|
||||
perms[schema.className] = schema.classLevelPermissions;
|
||||
});
|
||||
.then(allSchemas => {
|
||||
const data = {};
|
||||
const perms = {};
|
||||
allSchemas.forEach(schema => {
|
||||
data[schema.className] = injectDefaultSchema(schema).fields;
|
||||
perms[schema.className] = schema.classLevelPermissions;
|
||||
});
|
||||
|
||||
// Inject the in-memory classes
|
||||
volatileClasses.forEach(className => {
|
||||
const schema = injectDefaultSchema({ className });
|
||||
data[className] = schema.fields;
|
||||
perms[className] = schema.classLevelPermissions;
|
||||
// Inject the in-memory classes
|
||||
volatileClasses.forEach(className => {
|
||||
const schema = injectDefaultSchema({ className });
|
||||
data[className] = schema.fields;
|
||||
perms[className] = schema.classLevelPermissions;
|
||||
});
|
||||
this.data = data;
|
||||
this.perms = perms;
|
||||
delete this.reloadDataPromise;
|
||||
}, (err) => {
|
||||
this.data = {};
|
||||
this.perms = {};
|
||||
delete this.reloadDataPromise;
|
||||
throw err;
|
||||
});
|
||||
this.data = data;
|
||||
this.perms = perms;
|
||||
delete this.reloadDataPromise;
|
||||
}, (err) => {
|
||||
this.data = {};
|
||||
this.perms = {};
|
||||
delete this.reloadDataPromise;
|
||||
throw err;
|
||||
});
|
||||
return this.reloadDataPromise;
|
||||
}
|
||||
|
||||
@@ -417,12 +417,12 @@ export default class SchemaController {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
return this._dbAdapter.getClass(className)
|
||||
.then(injectDefaultSchema)
|
||||
.then((result) => {
|
||||
return this._cache.setOneSchema(className, result).then(() => {
|
||||
return result;
|
||||
})
|
||||
});
|
||||
.then(injectDefaultSchema)
|
||||
.then((result) => {
|
||||
return this._cache.setOneSchema(className, result).then(() => {
|
||||
return result;
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -441,84 +441,84 @@ export default class SchemaController {
|
||||
}
|
||||
|
||||
return this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({ fields, classLevelPermissions, className }))
|
||||
.then(convertAdapterSchemaToParseSchema)
|
||||
.then((res) => {
|
||||
return this._cache.clear().then(() => {
|
||||
return Promise.resolve(res);
|
||||
.then(convertAdapterSchemaToParseSchema)
|
||||
.then((res) => {
|
||||
return this._cache.clear().then(() => {
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateClass(className, submittedFields, classLevelPermissions, database) {
|
||||
return this.getOneSchema(className)
|
||||
.then(schema => {
|
||||
const existingFields = schema.fields;
|
||||
Object.keys(submittedFields).forEach(name => {
|
||||
const field = submittedFields[name];
|
||||
if (existingFields[name] && field.__op !== 'Delete') {
|
||||
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
|
||||
}
|
||||
if (!existingFields[name] && field.__op === 'Delete') {
|
||||
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
|
||||
}
|
||||
});
|
||||
|
||||
delete existingFields._rperm;
|
||||
delete existingFields._wperm;
|
||||
const newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
||||
const validationError = this.validateSchemaData(className, newSchema, classLevelPermissions, Object.keys(existingFields));
|
||||
if (validationError) {
|
||||
throw new Parse.Error(validationError.code, validationError.error);
|
||||
}
|
||||
|
||||
// Finally we have checked to make sure the request is valid and we can start deleting fields.
|
||||
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
|
||||
const deletedFields = [];
|
||||
const insertedFields = [];
|
||||
Object.keys(submittedFields).forEach(fieldName => {
|
||||
if (submittedFields[fieldName].__op === 'Delete') {
|
||||
deletedFields.push(fieldName);
|
||||
} else {
|
||||
insertedFields.push(fieldName);
|
||||
}
|
||||
});
|
||||
|
||||
let deletePromise = Promise.resolve();
|
||||
if (deletedFields.length > 0) {
|
||||
deletePromise = this.deleteFields(deletedFields, className, database);
|
||||
}
|
||||
|
||||
return deletePromise // Delete Everything
|
||||
.then(() => this.reloadData({ clearCache: true })) // Reload our Schema, so we have all the new values
|
||||
.then(() => {
|
||||
const promises = insertedFields.map(fieldName => {
|
||||
const type = submittedFields[fieldName];
|
||||
return this.enforceFieldExists(className, fieldName, type);
|
||||
.then(schema => {
|
||||
const existingFields = schema.fields;
|
||||
Object.keys(submittedFields).forEach(name => {
|
||||
const field = submittedFields[name];
|
||||
if (existingFields[name] && field.__op !== 'Delete') {
|
||||
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
|
||||
}
|
||||
if (!existingFields[name] && field.__op === 'Delete') {
|
||||
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
|
||||
}
|
||||
});
|
||||
return Promise.all(promises);
|
||||
|
||||
delete existingFields._rperm;
|
||||
delete existingFields._wperm;
|
||||
const newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
||||
const validationError = this.validateSchemaData(className, newSchema, classLevelPermissions, Object.keys(existingFields));
|
||||
if (validationError) {
|
||||
throw new Parse.Error(validationError.code, validationError.error);
|
||||
}
|
||||
|
||||
// Finally we have checked to make sure the request is valid and we can start deleting fields.
|
||||
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
|
||||
const deletedFields = [];
|
||||
const insertedFields = [];
|
||||
Object.keys(submittedFields).forEach(fieldName => {
|
||||
if (submittedFields[fieldName].__op === 'Delete') {
|
||||
deletedFields.push(fieldName);
|
||||
} else {
|
||||
insertedFields.push(fieldName);
|
||||
}
|
||||
});
|
||||
|
||||
let deletePromise = Promise.resolve();
|
||||
if (deletedFields.length > 0) {
|
||||
deletePromise = this.deleteFields(deletedFields, className, database);
|
||||
}
|
||||
|
||||
return deletePromise // Delete Everything
|
||||
.then(() => this.reloadData({ clearCache: true })) // Reload our Schema, so we have all the new values
|
||||
.then(() => {
|
||||
const promises = insertedFields.map(fieldName => {
|
||||
const type = submittedFields[fieldName];
|
||||
return this.enforceFieldExists(className, fieldName, type);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(() => this.setPermissions(className, classLevelPermissions, newSchema))
|
||||
//TODO: Move this logic into the database adapter
|
||||
.then(() => ({
|
||||
className: className,
|
||||
fields: this.data[className],
|
||||
classLevelPermissions: this.perms[className]
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(() => this.setPermissions(className, classLevelPermissions, newSchema))
|
||||
//TODO: Move this logic into the database adapter
|
||||
.then(() => ({
|
||||
className: className,
|
||||
fields: this.data[className],
|
||||
classLevelPermissions: this.perms[className]
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Returns a promise that resolves successfully to the new schema
|
||||
@@ -530,26 +530,26 @@ export default class SchemaController {
|
||||
// We don't have this class. Update the schema
|
||||
return this.addClassIfNotExists(className)
|
||||
// The schema update succeeded. Reload the schema
|
||||
.then(() => this.reloadData({ clearCache: true }))
|
||||
.catch(() => {
|
||||
.then(() => this.reloadData({ clearCache: true }))
|
||||
.catch(() => {
|
||||
// The schema update failed. This can be okay - it might
|
||||
// have failed because there's a race condition and a different
|
||||
// client is making the exact same schema update that we want.
|
||||
// So just reload the schema.
|
||||
return this.reloadData({ clearCache: true });
|
||||
})
|
||||
.then(() => {
|
||||
return this.reloadData({ clearCache: true });
|
||||
})
|
||||
.then(() => {
|
||||
// Ensure that the schema now validates
|
||||
if (this.data[className]) {
|
||||
return this;
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.data[className]) {
|
||||
return this;
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// The schema still doesn't validate. Give up
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||
});
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||
});
|
||||
}
|
||||
|
||||
validateNewClass(className, fields = {}, classLevelPermissions) {
|
||||
@@ -606,7 +606,7 @@ export default class SchemaController {
|
||||
}
|
||||
validateCLP(perms, newSchema);
|
||||
return this._dbAdapter.setClassLevelPermissions(className, perms)
|
||||
.then(() => this.reloadData({ clearCache: true }));
|
||||
.then(() => this.reloadData({ clearCache: true }));
|
||||
}
|
||||
|
||||
// Returns a promise that resolves successfully to the new schema
|
||||
@@ -694,35 +694,35 @@ export default class SchemaController {
|
||||
});
|
||||
|
||||
return this.getOneSchema(className, false, {clearCache: true})
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.then(schema => {
|
||||
fieldNames.forEach(fieldName => {
|
||||
if (!schema.fields[fieldName]) {
|
||||
throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`);
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
const schemaFields = { ...schema.fields };
|
||||
return database.adapter.deleteFields(className, schema, fieldNames)
|
||||
.then(() => {
|
||||
return Promise.all(fieldNames.map(fieldName => {
|
||||
const field = schemaFields[fieldName];
|
||||
if (field && field.type === 'Relation') {
|
||||
//For relations, drop the _Join table
|
||||
return database.adapter.deleteClass(`_Join:${fieldName}:${className}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}));
|
||||
})
|
||||
.then(schema => {
|
||||
fieldNames.forEach(fieldName => {
|
||||
if (!schema.fields[fieldName]) {
|
||||
throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`);
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
this._cache.clear();
|
||||
});
|
||||
|
||||
const schemaFields = { ...schema.fields };
|
||||
return database.adapter.deleteFields(className, schema, fieldNames)
|
||||
.then(() => {
|
||||
return Promise.all(fieldNames.map(fieldName => {
|
||||
const field = schemaFields[fieldName];
|
||||
if (field && field.type === 'Relation') {
|
||||
//For relations, drop the _Join table
|
||||
return database.adapter.deleteClass(`_Join:${fieldName}:${className}`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}));
|
||||
});
|
||||
}).then(() => {
|
||||
this._cache.clear();
|
||||
});
|
||||
}
|
||||
|
||||
// Validates an object provided in REST format.
|
||||
@@ -825,10 +825,10 @@ export default class SchemaController {
|
||||
// If aclGroup has * (public)
|
||||
if (!aclGroup || aclGroup.length == 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.');
|
||||
'Permission denied, user needs to be authenticated.');
|
||||
} else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.');
|
||||
'Permission denied, user needs to be authenticated.');
|
||||
}
|
||||
// requiresAuthentication passed, just move forward
|
||||
// probably would be wise at some point to rename to 'authenticatedUser'
|
||||
@@ -850,7 +850,7 @@ export default class SchemaController {
|
||||
return Promise.resolve();
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Permission denied for action ${operation} on class ${className}.`);
|
||||
`Permission denied for action ${operation} on class ${className}.`);
|
||||
}
|
||||
|
||||
// Returns the expected type for a className+key combination
|
||||
|
||||
@@ -164,25 +164,25 @@ export class UserController extends AdaptableController {
|
||||
}
|
||||
|
||||
return this.setPasswordResetToken(email)
|
||||
.then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
.then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
};
|
||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
};
|
||||
|
||||
if (this.adapter.sendPasswordResetEmail) {
|
||||
this.adapter.sendPasswordResetEmail(options);
|
||||
} else {
|
||||
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||
}
|
||||
if (this.adapter.sendPasswordResetEmail) {
|
||||
this.adapter.sendPasswordResetEmail(options);
|
||||
} else {
|
||||
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||
}
|
||||
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
}
|
||||
|
||||
updatePassword(username, token, password) {
|
||||
|
||||
@@ -338,45 +338,45 @@ class ParseLiveQueryServer {
|
||||
}
|
||||
|
||||
this.sessionTokenCache.getUserId(subscriptionSessionToken)
|
||||
.then((userId) => {
|
||||
.then((userId) => {
|
||||
|
||||
// Pass along a null if there is no user id
|
||||
if (!userId) {
|
||||
return Parse.Promise.as(null);
|
||||
}
|
||||
if (!userId) {
|
||||
return Parse.Promise.as(null);
|
||||
}
|
||||
|
||||
// Prepare a user object to query for roles
|
||||
// To eliminate a query for the user, create one locally with the id
|
||||
var user = new Parse.User();
|
||||
user.id = userId;
|
||||
return user;
|
||||
var user = new Parse.User();
|
||||
user.id = userId;
|
||||
return user;
|
||||
|
||||
})
|
||||
.then((user) => {
|
||||
})
|
||||
.then((user) => {
|
||||
|
||||
// Pass along an empty array (of roles) if no user
|
||||
if (!user) {
|
||||
return Parse.Promise.as([]);
|
||||
}
|
||||
if (!user) {
|
||||
return Parse.Promise.as([]);
|
||||
}
|
||||
|
||||
// Then get the user's roles
|
||||
var rolesQuery = new Parse.Query(Parse.Role);
|
||||
rolesQuery.equalTo("users", user);
|
||||
return rolesQuery.find({useMasterKey:true});
|
||||
}).
|
||||
then((roles) => {
|
||||
var rolesQuery = new Parse.Query(Parse.Role);
|
||||
rolesQuery.equalTo("users", user);
|
||||
return rolesQuery.find({useMasterKey:true});
|
||||
}).
|
||||
then((roles) => {
|
||||
|
||||
// Finally, see if any of the user's roles allow them read access
|
||||
for (const role of roles) {
|
||||
if (acl.getRoleReadAccess(role)) {
|
||||
return resolve(true);
|
||||
for (const role of roles) {
|
||||
if (acl.getRoleReadAccess(role)) {
|
||||
return resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
resolve(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
});
|
||||
}).then((isRoleMatched) => {
|
||||
|
||||
@@ -227,8 +227,8 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
const propertyExists = typeof object[key] !== 'undefined';
|
||||
const existenceIsRequired = constraints['$exists'];
|
||||
if (typeof constraints['$exists'] !== 'boolean') {
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if ((!propertyExists && existenceIsRequired) || (propertyExists && !existenceIsRequired)) {
|
||||
@@ -240,17 +240,17 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
if (typeof compareTo === 'object') {
|
||||
return compareTo.test(object[key]);
|
||||
}
|
||||
// JS doesn't support perl-style escaping
|
||||
// JS doesn't support perl-style escaping
|
||||
var expString = '';
|
||||
var escapeEnd = -2;
|
||||
var escapeStart = compareTo.indexOf('\\Q');
|
||||
while (escapeStart > -1) {
|
||||
// Add the unescaped portion
|
||||
// Add the unescaped portion
|
||||
expString += compareTo.substring(escapeEnd + 2, escapeStart);
|
||||
escapeEnd = compareTo.indexOf('\\E', escapeStart);
|
||||
if (escapeEnd > -1) {
|
||||
expString += compareTo.substring(escapeStart + 2, escapeEnd)
|
||||
.replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&');
|
||||
.replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&');
|
||||
}
|
||||
|
||||
escapeStart = compareTo.indexOf('\\Q', escapeEnd);
|
||||
@@ -270,22 +270,22 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
var northEast = compareTo.$box[1];
|
||||
if (southWest.latitude > northEast.latitude ||
|
||||
southWest.longitude > northEast.longitude) {
|
||||
// Invalid box, crosses the date line
|
||||
// Invalid box, crosses the date line
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
object[key].latitude > southWest.latitude &&
|
||||
object[key].latitude > southWest.latitude &&
|
||||
object[key].latitude < northEast.latitude &&
|
||||
object[key].longitude > southWest.longitude &&
|
||||
object[key].longitude < northEast.longitude
|
||||
);
|
||||
case '$options':
|
||||
// Not a query type, but a way to add options to $regex. Ignore and
|
||||
// avoid the default
|
||||
// Not a query type, but a way to add options to $regex. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$maxDistance':
|
||||
// Not a query type, but a way to add a cap to $nearSphere. Ignore and
|
||||
// avoid the default
|
||||
// Not a query type, but a way to add a cap to $nearSphere. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$select':
|
||||
return false;
|
||||
|
||||
@@ -34,10 +34,10 @@ export class PushQueue {
|
||||
}
|
||||
return Promise.resolve().then(() => {
|
||||
return rest.find(config,
|
||||
auth,
|
||||
'_Installation',
|
||||
where,
|
||||
{limit: 0, count: true});
|
||||
auth,
|
||||
'_Installation',
|
||||
where,
|
||||
{limit: 0, count: true});
|
||||
}).then(({results, count}) => {
|
||||
if (!results) {
|
||||
return Promise.reject({error: 'PushController: no results in query'})
|
||||
|
||||
@@ -24,7 +24,7 @@ export function validatePushType(where = {}, validPushTypes = []) {
|
||||
var deviceType = deviceTypes[i];
|
||||
if (validPushTypes.indexOf(deviceType) < 0) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
deviceType + ' is not supported push type.');
|
||||
deviceType + ' is not supported push type.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
|
||||
if (this.className == '_Session') {
|
||||
if (!this.findOptions.acl) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'This session token is invalid.');
|
||||
'This session token is invalid.');
|
||||
}
|
||||
this.restWhere = {
|
||||
'$and': [this.restWhere, {
|
||||
@@ -130,7 +130,7 @@ function RestQuery(config, auth, className, restWhere = {}, restOptions = {}, cl
|
||||
break;
|
||||
default:
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'bad option: ' + option);
|
||||
'bad option: ' + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,9 +199,9 @@ RestQuery.prototype.redirectClassNameForKey = function() {
|
||||
// We need to change the class name based on the schema
|
||||
return this.config.database.redirectClassNameForKey(
|
||||
this.className, this.redirectKey).then((newClassName) => {
|
||||
this.className = newClassName;
|
||||
this.redirectClassName = newClassName;
|
||||
});
|
||||
this.className = newClassName;
|
||||
this.redirectClassName = newClassName;
|
||||
});
|
||||
};
|
||||
|
||||
// Validates this operation against the allowClientClassCreation config.
|
||||
@@ -213,7 +213,7 @@ RestQuery.prototype.validateClientClassCreation = function() {
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' +
|
||||
'This user is not allowed to access ' +
|
||||
'non-existent class: ' + this.className);
|
||||
}
|
||||
});
|
||||
@@ -253,7 +253,7 @@ RestQuery.prototype.replaceInQuery = function() {
|
||||
var inQueryValue = inQueryObject['$inQuery'];
|
||||
if (!inQueryValue.where || !inQueryValue.className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $inQuery');
|
||||
'improper usage of $inQuery');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -301,7 +301,7 @@ RestQuery.prototype.replaceNotInQuery = function() {
|
||||
var notInQueryValue = notInQueryObject['$notInQuery'];
|
||||
if (!notInQueryValue.where || !notInQueryValue.className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $notInQuery');
|
||||
'improper usage of $notInQuery');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -351,7 +351,7 @@ RestQuery.prototype.replaceSelect = function() {
|
||||
!selectValue.query.className ||
|
||||
Object.keys(selectValue).length !== 2) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $select');
|
||||
'improper usage of $select');
|
||||
}
|
||||
|
||||
const additionalOptions = {
|
||||
@@ -400,7 +400,7 @@ RestQuery.prototype.replaceDontSelect = function() {
|
||||
!dontSelectValue.query.className ||
|
||||
Object.keys(dontSelectValue).length !== 2) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'improper usage of $dontSelect');
|
||||
'improper usage of $dontSelect');
|
||||
}
|
||||
const additionalOptions = {
|
||||
redirectClassNameForKey: dontSelectValue.query.redirectClassNameForKey
|
||||
@@ -493,22 +493,22 @@ RestQuery.prototype.runFind = function(options = {}) {
|
||||
}
|
||||
return this.config.database.find(
|
||||
this.className, this.restWhere, findOptions).then((results) => {
|
||||
if (this.className === '_User') {
|
||||
for (var result of results) {
|
||||
cleanResultOfSensitiveUserInfo(result, this.auth, this.config);
|
||||
cleanResultAuthData(result);
|
||||
}
|
||||
if (this.className === '_User') {
|
||||
for (var result of results) {
|
||||
cleanResultOfSensitiveUserInfo(result, this.auth, this.config);
|
||||
cleanResultAuthData(result);
|
||||
}
|
||||
}
|
||||
|
||||
this.config.filesController.expandFilesInObject(this.config, results);
|
||||
this.config.filesController.expandFilesInObject(this.config, results);
|
||||
|
||||
if (this.redirectClassName) {
|
||||
for (var r of results) {
|
||||
r.className = this.redirectClassName;
|
||||
}
|
||||
if (this.redirectClassName) {
|
||||
for (var r of results) {
|
||||
r.className = this.redirectClassName;
|
||||
}
|
||||
this.response = {results: results};
|
||||
});
|
||||
}
|
||||
this.response = {results: results};
|
||||
});
|
||||
};
|
||||
|
||||
// Returns a promise for whether it was successful.
|
||||
@@ -522,8 +522,8 @@ RestQuery.prototype.runCount = function() {
|
||||
delete this.findOptions.limit;
|
||||
return this.config.database.find(
|
||||
this.className, this.restWhere, this.findOptions).then((c) => {
|
||||
this.response.count = c;
|
||||
});
|
||||
this.response.count = c;
|
||||
});
|
||||
};
|
||||
|
||||
// Augments this.response with data at the paths provided in this.include.
|
||||
@@ -533,7 +533,7 @@ RestQuery.prototype.handleInclude = function() {
|
||||
}
|
||||
|
||||
var pathResponse = includePath(this.config, this.auth,
|
||||
this.response, this.include[0], this.restOptions);
|
||||
this.response, this.include[0], this.restOptions);
|
||||
if (pathResponse.then) {
|
||||
return pathResponse.then((newResponse) => {
|
||||
this.response = newResponse;
|
||||
@@ -681,7 +681,7 @@ function findPointers(object, path) {
|
||||
function replacePointers(object, path, replace) {
|
||||
if (object instanceof Array) {
|
||||
return object.map((obj) => replacePointers(obj, path, replace))
|
||||
.filter((obj) => typeof obj !== 'undefined');
|
||||
.filter((obj) => typeof obj !== 'undefined');
|
||||
}
|
||||
|
||||
if (typeof object !== 'object' || !object) {
|
||||
|
||||
128
src/RestWrite.js
128
src/RestWrite.js
@@ -120,7 +120,7 @@ RestWrite.prototype.validateClientClassCreation = function() {
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' +
|
||||
'This user is not allowed to access ' +
|
||||
'non-existent class: ' + this.className);
|
||||
}
|
||||
});
|
||||
@@ -205,11 +205,11 @@ RestWrite.prototype.validateAuthData = function() {
|
||||
if (!this.query && !this.data.authData) {
|
||||
if (typeof this.data.username !== 'string' || _.isEmpty(this.data.username)) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_MISSING,
|
||||
'bad or missing username');
|
||||
'bad or missing username');
|
||||
}
|
||||
if (typeof this.data.password !== 'string' || _.isEmpty(this.data.password)) {
|
||||
throw new Parse.Error(Parse.Error.PASSWORD_MISSING,
|
||||
'password is required');
|
||||
'password is required');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ RestWrite.prototype.validateAuthData = function() {
|
||||
}
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||
'This authentication method is unsupported.');
|
||||
'This authentication method is unsupported.');
|
||||
};
|
||||
|
||||
RestWrite.prototype.handleAuthDataValidation = function(authData) {
|
||||
@@ -241,7 +241,7 @@ RestWrite.prototype.handleAuthDataValidation = function(authData) {
|
||||
const validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
|
||||
if (!validateAuthData) {
|
||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||
'This authentication method is unsupported.');
|
||||
'This authentication method is unsupported.');
|
||||
}
|
||||
return validateAuthData(authData[provider]);
|
||||
});
|
||||
@@ -266,8 +266,8 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) {
|
||||
let findPromise = Promise.resolve([]);
|
||||
if (query.length > 0) {
|
||||
findPromise = this.config.database.find(
|
||||
this.className,
|
||||
{'$or': query}, {})
|
||||
this.className,
|
||||
{'$or': query}, {})
|
||||
}
|
||||
|
||||
return findPromise;
|
||||
@@ -281,7 +281,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
||||
if (results.length > 1) {
|
||||
// More than 1 user with the passed id's
|
||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||
'this auth is already used');
|
||||
'this auth is already used');
|
||||
}
|
||||
|
||||
this.storage['authProvider'] = Object.keys(authData).join(',');
|
||||
@@ -333,7 +333,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
||||
// are different
|
||||
if (userResult.objectId !== this.query.objectId) {
|
||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||
'this auth is already used');
|
||||
'this auth is already used');
|
||||
}
|
||||
// No auth data was mutated, just keep going
|
||||
if (!hasMutatedAuthData) {
|
||||
@@ -582,13 +582,13 @@ RestWrite.prototype.handleFollowup = function() {
|
||||
};
|
||||
delete this.storage['clearSessions'];
|
||||
return this.config.database.destroy('_Session', sessionQuery)
|
||||
.then(this.handleFollowup.bind(this));
|
||||
.then(this.handleFollowup.bind(this));
|
||||
}
|
||||
|
||||
if (this.storage && this.storage['generateNewSession']) {
|
||||
delete this.storage['generateNewSession'];
|
||||
return this.createSessionToken()
|
||||
.then(this.handleFollowup.bind(this));
|
||||
.then(this.handleFollowup.bind(this));
|
||||
}
|
||||
|
||||
if (this.storage && this.storage['sendVerificationEmail']) {
|
||||
@@ -608,7 +608,7 @@ RestWrite.prototype.handleSession = function() {
|
||||
|
||||
if (!this.auth.user && !this.auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token required.');
|
||||
'Session token required.');
|
||||
}
|
||||
|
||||
// TODO: Verify proper error to throw
|
||||
@@ -643,7 +643,7 @@ RestWrite.prototype.handleSession = function() {
|
||||
return create.execute().then((results) => {
|
||||
if (!results.response) {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR,
|
||||
'Error creating session.');
|
||||
'Error creating session.');
|
||||
}
|
||||
sessionData['objectId'] = results.response['objectId'];
|
||||
this.response = {
|
||||
@@ -667,7 +667,7 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
|
||||
if (!this.query && !this.data.deviceToken && !this.data.installationId && !this.auth.installationId) {
|
||||
throw new Parse.Error(135,
|
||||
'at least one ID field (deviceToken, installationId) ' +
|
||||
'at least one ID field (deviceToken, installationId) ' +
|
||||
'must be specified in this operation');
|
||||
}
|
||||
|
||||
@@ -747,25 +747,25 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
if (this.query && this.query.objectId) {
|
||||
if (!objectIdMatch) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for update.');
|
||||
'Object not found for update.');
|
||||
}
|
||||
if (this.data.installationId && objectIdMatch.installationId &&
|
||||
this.data.installationId !== objectIdMatch.installationId) {
|
||||
throw new Parse.Error(136,
|
||||
'installationId may not be changed in this ' +
|
||||
'installationId may not be changed in this ' +
|
||||
'operation');
|
||||
}
|
||||
if (this.data.deviceToken && objectIdMatch.deviceToken &&
|
||||
this.data.deviceToken !== objectIdMatch.deviceToken &&
|
||||
!this.data.installationId && !objectIdMatch.installationId) {
|
||||
throw new Parse.Error(136,
|
||||
'deviceToken may not be changed in this ' +
|
||||
'deviceToken may not be changed in this ' +
|
||||
'operation');
|
||||
}
|
||||
if (this.data.deviceType && this.data.deviceType &&
|
||||
this.data.deviceType !== objectIdMatch.deviceType) {
|
||||
throw new Parse.Error(136,
|
||||
'deviceType may not be changed in this ' +
|
||||
'deviceType may not be changed in this ' +
|
||||
'operation');
|
||||
}
|
||||
}
|
||||
@@ -780,7 +780,7 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
// need to specify deviceType only if it's new
|
||||
if (!this.query && !this.data.deviceType && !idMatch) {
|
||||
throw new Parse.Error(135,
|
||||
'deviceType must be specified in this operation');
|
||||
'deviceType must be specified in this operation');
|
||||
}
|
||||
|
||||
}).then(() => {
|
||||
@@ -796,7 +796,7 @@ RestWrite.prototype.handleInstallation = function() {
|
||||
return deviceTokenMatches[0]['objectId'];
|
||||
} else if (!this.data.installationId) {
|
||||
throw new Parse.Error(132,
|
||||
'Must specify installationId when deviceToken ' +
|
||||
'Must specify installationId when deviceToken ' +
|
||||
'matches multiple Installation objects');
|
||||
} else {
|
||||
// Multiple device token matches and we specified an installation ID,
|
||||
@@ -968,11 +968,11 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
||||
return defer.then(() => {
|
||||
// Run an update
|
||||
return this.config.database.update(this.className, this.query, this.data, this.runOptions)
|
||||
.then(response => {
|
||||
response.updatedAt = this.updatedAt;
|
||||
this._updateResponseWithData(response, this.data);
|
||||
this.response = { response };
|
||||
});
|
||||
.then(response => {
|
||||
response.updatedAt = this.updatedAt;
|
||||
this._updateResponseWithData(response, this.data);
|
||||
this.response = { response };
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Set the default ACL and password timestamp for the new _User
|
||||
@@ -994,50 +994,50 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
||||
|
||||
// Run a create
|
||||
return this.config.database.create(this.className, this.data, this.runOptions)
|
||||
.catch(error => {
|
||||
if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||
throw error;
|
||||
}
|
||||
// If this was a failed user creation due to username or email already taken, we need to
|
||||
// check whether it was username or email and return the appropriate error.
|
||||
|
||||
// TODO: See if we can later do this without additional queries by using named indexes.
|
||||
return this.config.database.find(
|
||||
this.className,
|
||||
{ username: this.data.username, objectId: {'$ne': this.objectId()} },
|
||||
{ limit: 1 }
|
||||
)
|
||||
.then(results => {
|
||||
if (results.length > 0) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
|
||||
.catch(error => {
|
||||
if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||
throw error;
|
||||
}
|
||||
// If this was a failed user creation due to username or email already taken, we need to
|
||||
// check whether it was username or email and return the appropriate error.
|
||||
|
||||
// TODO: See if we can later do this without additional queries by using named indexes.
|
||||
return this.config.database.find(
|
||||
this.className,
|
||||
{ email: this.data.email, objectId: {'$ne': this.objectId()} },
|
||||
{ username: this.data.username, objectId: {'$ne': this.objectId()} },
|
||||
{ limit: 1 }
|
||||
);
|
||||
)
|
||||
.then(results => {
|
||||
if (results.length > 0) {
|
||||
throw new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.');
|
||||
}
|
||||
return this.config.database.find(
|
||||
this.className,
|
||||
{ email: this.data.email, objectId: {'$ne': this.objectId()} },
|
||||
{ limit: 1 }
|
||||
);
|
||||
})
|
||||
.then(results => {
|
||||
if (results.length > 0) {
|
||||
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
});
|
||||
})
|
||||
.then(results => {
|
||||
if (results.length > 0) {
|
||||
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.');
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided');
|
||||
});
|
||||
})
|
||||
.then(response => {
|
||||
response.objectId = this.data.objectId;
|
||||
response.createdAt = this.data.createdAt;
|
||||
.then(response => {
|
||||
response.objectId = this.data.objectId;
|
||||
response.createdAt = this.data.createdAt;
|
||||
|
||||
if (this.responseShouldHaveUsername) {
|
||||
response.username = this.data.username;
|
||||
}
|
||||
this._updateResponseWithData(response, this.data);
|
||||
this.response = {
|
||||
status: 201,
|
||||
response,
|
||||
location: this.location()
|
||||
};
|
||||
});
|
||||
if (this.responseShouldHaveUsername) {
|
||||
response.username = this.data.username;
|
||||
}
|
||||
this._updateResponseWithData(response, this.data);
|
||||
this.response = {
|
||||
status: 201,
|
||||
response,
|
||||
location: this.location()
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1080,7 +1080,7 @@ RestWrite.prototype.runAfterTrigger = function() {
|
||||
// A helper to figure out what location this operation happens at.
|
||||
RestWrite.prototype.location = function() {
|
||||
var middle = (this.className === '_User' ? '/users/' :
|
||||
'/classes/' + this.className + '/');
|
||||
'/classes/' + this.className + '/');
|
||||
return this.config.mount + middle + this.data.objectId;
|
||||
};
|
||||
|
||||
|
||||
@@ -130,10 +130,10 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result));
|
||||
logger.info(`Ran cloud function ${functionName} for user ${userString} `
|
||||
+ `with:\n Input: ${cleanInput }\n Result: ${cleanResult }`, {
|
||||
functionName,
|
||||
params,
|
||||
user: userString,
|
||||
});
|
||||
functionName,
|
||||
params,
|
||||
user: userString,
|
||||
});
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@@ -143,11 +143,11 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
logger.error(`Failed running cloud function ${functionName} for `
|
||||
+ `user ${userString} with:\n Input: ${cleanInput}\n Error: `
|
||||
+ JSON.stringify(error), {
|
||||
functionName,
|
||||
error,
|
||||
params,
|
||||
user: userString
|
||||
});
|
||||
functionName,
|
||||
error,
|
||||
params,
|
||||
user: userString
|
||||
});
|
||||
reject(error);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
|
||||
@@ -5,15 +5,15 @@ export class PurgeRouter extends PromiseRouter {
|
||||
|
||||
handlePurge(req) {
|
||||
return req.config.database.purgeCollection(req.params.className)
|
||||
.then(() => {
|
||||
var cacheAdapter = req.config.cacheController;
|
||||
if (req.params.className == '_Session') {
|
||||
cacheAdapter.user.clear();
|
||||
} else if (req.params.className == '_Role') {
|
||||
cacheAdapter.role.clear();
|
||||
}
|
||||
return {response: {}};
|
||||
});
|
||||
.then(() => {
|
||||
var cacheAdapter = req.config.cacheController;
|
||||
if (req.params.className == '_Session') {
|
||||
cacheAdapter.user.clear();
|
||||
} else if (req.params.className == '_Role') {
|
||||
cacheAdapter.role.clear();
|
||||
}
|
||||
return {response: {}};
|
||||
});
|
||||
}
|
||||
|
||||
mountRoutes() {
|
||||
|
||||
@@ -15,22 +15,22 @@ function classNameMismatchResponse(bodyClass, pathClass) {
|
||||
|
||||
function getAllSchemas(req) {
|
||||
return req.config.database.loadSchema({ clearCache: true})
|
||||
.then(schemaController => schemaController.getAllClasses(true))
|
||||
.then(schemas => ({ response: { results: schemas } }));
|
||||
.then(schemaController => schemaController.getAllClasses(true))
|
||||
.then(schemas => ({ response: { results: schemas } }));
|
||||
}
|
||||
|
||||
function getOneSchema(req) {
|
||||
const className = req.params.className;
|
||||
return req.config.database.loadSchema({ clearCache: true})
|
||||
.then(schemaController => schemaController.getOneSchema(className, true))
|
||||
.then(schema => ({ response: schema }))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
|
||||
}
|
||||
});
|
||||
.then(schemaController => schemaController.getOneSchema(className, true))
|
||||
.then(schema => ({ response: schema }))
|
||||
.catch(error => {
|
||||
if (error === undefined) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createSchema(req) {
|
||||
@@ -59,8 +59,8 @@ function modifySchema(req) {
|
||||
const className = req.params.className;
|
||||
|
||||
return req.config.database.loadSchema({ clearCache: true})
|
||||
.then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database))
|
||||
.then(result => ({response: result}));
|
||||
.then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database))
|
||||
.then(result => ({response: result}));
|
||||
}
|
||||
|
||||
const deleteSchema = req => {
|
||||
@@ -68,7 +68,7 @@ const deleteSchema = req => {
|
||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className));
|
||||
}
|
||||
return req.config.database.deleteSchema(req.params.className)
|
||||
.then(() => ({ response: {} }));
|
||||
.then(() => ({ response: {} }));
|
||||
}
|
||||
|
||||
export class SchemasRouter extends PromiseRouter {
|
||||
|
||||
@@ -159,7 +159,7 @@ export function pushStatusHandler(config, objectId = newObjectId()) {
|
||||
const setRunning = function(count) {
|
||||
logger.verbose(`_PushStatus ${objectId}: sending push to %d installations`, count);
|
||||
return handler.update({status:"pending", objectId: objectId},
|
||||
{status: "running", updatedAt: new Date(), count });
|
||||
{status: "running", updatedAt: new Date(), count });
|
||||
}
|
||||
|
||||
const trackSent = function(results) {
|
||||
|
||||
16
src/batch.js
16
src/batch.js
@@ -26,11 +26,11 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
let apiPrefix = originalUrl.slice(0, apiPrefixLength);
|
||||
|
||||
const makeRoutablePath = function(requestPath) {
|
||||
// The routablePath is the path minus the api prefix
|
||||
// The routablePath is the path minus the api prefix
|
||||
if (requestPath.slice(0, apiPrefix.length) != apiPrefix) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
'cannot route batch path ' + requestPath);
|
||||
Parse.Error.INVALID_JSON,
|
||||
'cannot route batch path ' + requestPath);
|
||||
}
|
||||
return path.posix.join('/', requestPath.slice(apiPrefix.length));
|
||||
}
|
||||
@@ -39,13 +39,13 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
&& (serverURL.path != publicServerURL.path)) {
|
||||
const localPath = serverURL.path;
|
||||
const publicPath = publicServerURL.path;
|
||||
// Override the api prefix
|
||||
// Override the api prefix
|
||||
apiPrefix = localPath;
|
||||
return function(requestPath) {
|
||||
// Build the new path by removing the public path
|
||||
// and joining with the local path
|
||||
// Build the new path by removing the public path
|
||||
// and joining with the local path
|
||||
const newPath = path.posix.join('/', localPath, '/' , requestPath.slice(publicPath.length));
|
||||
// Use the method for local routing
|
||||
// Use the method for local routing
|
||||
return makeRoutablePath(newPath);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) {
|
||||
function handleBatch(router, req) {
|
||||
if (!Array.isArray(req.body.requests)) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'requests must be an array');
|
||||
'requests must be an array');
|
||||
}
|
||||
|
||||
// The batch paths are all from the root of our domain.
|
||||
|
||||
@@ -38,7 +38,7 @@ Command.prototype.loadDefinitions = function(definitions) {
|
||||
return object;
|
||||
}, {});
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* istanbul ignore next */
|
||||
this.on('--help', function(){
|
||||
console.log(' Configure From Environment:');
|
||||
console.log('');
|
||||
|
||||
40
src/rest.js
40
src/rest.js
@@ -46,12 +46,12 @@ const get = (config, auth, className, objectId, restOptions, clientSDK) => {
|
||||
function del(config, auth, className, objectId) {
|
||||
if (typeof objectId !== 'string') {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'bad objectId');
|
||||
'bad objectId');
|
||||
}
|
||||
|
||||
if (className === '_User' && !auth.couldUpdateUserId(objectId)) {
|
||||
throw new Parse.Error(Parse.Error.SESSION_MISSING,
|
||||
'insufficient auth to delete user');
|
||||
'insufficient auth to delete user');
|
||||
}
|
||||
|
||||
enforceRoleSecurity('delete', className, auth);
|
||||
@@ -63,25 +63,25 @@ function del(config, auth, className, objectId) {
|
||||
const hasLiveQuery = checkLiveQuery(className, config);
|
||||
if (hasTriggers || hasLiveQuery || className == '_Session') {
|
||||
return find(config, Auth.master(config), className, {objectId: objectId})
|
||||
.then((response) => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token');
|
||||
.then((response) => {
|
||||
if (response && response.results && response.results.length) {
|
||||
const firstResult = response.results[0];
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid session token');
|
||||
}
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
// Notify LiveQuery server if possible
|
||||
config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject);
|
||||
return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config);
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
cacheAdapter.user.del(firstResult.sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(firstResult);
|
||||
// Notify LiveQuery server if possible
|
||||
config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject);
|
||||
return triggers.maybeRunTrigger(triggers.Types.beforeDelete, auth, inflatedObject, null, config);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for delete.');
|
||||
});
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for delete.');
|
||||
});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
}).then(() => {
|
||||
@@ -140,7 +140,7 @@ function update(config, auth, className, restWhere, restObject, clientSDK) {
|
||||
}
|
||||
|
||||
const classesWithMasterOnlyAccess = ['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig', '_JobSchedule'];
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
|
||||
@@ -368,11 +368,11 @@ export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObj
|
||||
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config);
|
||||
var response = getResponseObject(request, (object) => {
|
||||
logTriggerSuccessBeforeHook(
|
||||
triggerType, parseObject.className, parseObject.toJSON(), object, auth);
|
||||
triggerType, parseObject.className, parseObject.toJSON(), object, auth);
|
||||
resolve(object);
|
||||
}, (error) => {
|
||||
logTriggerErrorBeforeHook(
|
||||
triggerType, parseObject.className, parseObject.toJSON(), auth, error);
|
||||
triggerType, parseObject.className, parseObject.toJSON(), auth, error);
|
||||
reject(error);
|
||||
});
|
||||
// Force the current Parse app before the trigger
|
||||
|
||||
26
src/vendor/mongodbUrl.js
vendored
26
src/vendor/mongodbUrl.js
vendored
@@ -242,14 +242,14 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
|
||||
case 123: // '{'
|
||||
case 124: // '|'
|
||||
case 125: // '}'
|
||||
// Characters that are never ever allowed in a hostname from RFC 2396
|
||||
// Characters that are never ever allowed in a hostname from RFC 2396
|
||||
if (nonHost === -1)
|
||||
nonHost = i;
|
||||
break;
|
||||
case 35: // '#'
|
||||
case 47: // '/'
|
||||
case 63: // '?'
|
||||
// Find the first instance of any host-ending characters
|
||||
// Find the first instance of any host-ending characters
|
||||
if (nonHost === -1)
|
||||
nonHost = i;
|
||||
hostEnd = i;
|
||||
@@ -371,8 +371,8 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
|
||||
|
||||
var firstIdx = (questionIdx !== -1 &&
|
||||
(hashIdx === -1 || questionIdx < hashIdx)
|
||||
? questionIdx
|
||||
: hashIdx);
|
||||
? questionIdx
|
||||
: hashIdx);
|
||||
if (firstIdx === -1) {
|
||||
if (rest.length > 0)
|
||||
this.pathname = rest;
|
||||
@@ -570,8 +570,8 @@ Url.prototype.format = function() {
|
||||
host = auth + this.host;
|
||||
} else if (this.hostname) {
|
||||
host = auth + (this.hostname.indexOf(':') === -1 ?
|
||||
this.hostname :
|
||||
'[' + this.hostname + ']');
|
||||
this.hostname :
|
||||
'[' + this.hostname + ']');
|
||||
if (this.port) {
|
||||
host += ':' + this.port;
|
||||
}
|
||||
@@ -742,7 +742,7 @@ Url.prototype.resolveObject = function(relative) {
|
||||
|
||||
var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/');
|
||||
var isRelAbs = (
|
||||
relative.host ||
|
||||
relative.host ||
|
||||
relative.pathname && relative.pathname.charAt(0) === '/'
|
||||
);
|
||||
var mustEndAbs = (isRelAbs || isSourceAbs ||
|
||||
@@ -780,9 +780,9 @@ Url.prototype.resolveObject = function(relative) {
|
||||
if (isRelAbs) {
|
||||
// it's absolute.
|
||||
result.host = (relative.host || relative.host === '') ?
|
||||
relative.host : result.host;
|
||||
relative.host : result.host;
|
||||
result.hostname = (relative.hostname || relative.hostname === '') ?
|
||||
relative.hostname : result.hostname;
|
||||
relative.hostname : result.hostname;
|
||||
result.search = relative.search;
|
||||
result.query = relative.query;
|
||||
srcPath = relPath;
|
||||
@@ -805,7 +805,7 @@ Url.prototype.resolveObject = function(relative) {
|
||||
//this especially happens in cases like
|
||||
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
||||
const authInHost = result.host && result.host.indexOf('@') > 0 ?
|
||||
result.host.split('@') : false;
|
||||
result.host.split('@') : false;
|
||||
if (authInHost) {
|
||||
result.auth = authInHost.shift();
|
||||
result.host = result.hostname = authInHost.shift();
|
||||
@@ -841,7 +841,7 @@ Url.prototype.resolveObject = function(relative) {
|
||||
// then it must NOT get a trailing slash.
|
||||
var last = srcPath.slice(-1)[0];
|
||||
var hasTrailingSlash = (
|
||||
(result.host || relative.host || srcPath.length > 1) &&
|
||||
(result.host || relative.host || srcPath.length > 1) &&
|
||||
(last === '.' || last === '..') || last === '');
|
||||
|
||||
// strip single dots, resolve double dots to parent dir
|
||||
@@ -882,12 +882,12 @@ Url.prototype.resolveObject = function(relative) {
|
||||
// put the host back
|
||||
if (psychotic) {
|
||||
result.hostname = result.host = isAbsolute ? '' :
|
||||
srcPath.length ? srcPath.shift() : '';
|
||||
srcPath.length ? srcPath.shift() : '';
|
||||
//occasionally the auth can get stuck only in host
|
||||
//this especially happens in cases like
|
||||
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
||||
const authInHost = result.host && result.host.indexOf('@') > 0 ?
|
||||
result.host.split('@') : false;
|
||||
result.host.split('@') : false;
|
||||
if (authInHost) {
|
||||
result.auth = authInHost.shift();
|
||||
result.host = result.hostname = authInHost.shift();
|
||||
|
||||
Reference in New Issue
Block a user