Skip to content

Fix for #1840, Strip operations from results, forwards delete operations to SDKs #1946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"redis": "2.6.2",
"request": "2.73.0",
"request-promise": "3.0.0",
"semver": "^5.2.0",
"tv4": "1.2.7",
"winston": "2.2.0",
"winston-daily-rotate-file": "1.0.1",
Expand Down
41 changes: 41 additions & 0 deletions spec/ClientSDK.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var ClientSDK = require('../src/ClientSDK');

describe('ClientSDK', () => {
it('should properly parse the SDK versions', () => {
let clientSDKFromVersion = ClientSDK.fromString;
expect(clientSDKFromVersion('i1.1.1')).toEqual({
sdk: 'i',
version: '1.1.1'
});
expect(clientSDKFromVersion('i1')).toEqual({
sdk: 'i',
version: '1'
});
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
sdk: 'apple-tv',
version: '1.13.0'
});
expect(clientSDKFromVersion('js1.9.0')).toEqual({
sdk: 'js',
version: '1.9.0'
});
});

it('should properly sastisfy', () => {
expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js1.9.0")).toBe(true);

expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js2.0.0")).toBe(true);

expect(ClientSDK.compatible({
js: '>=1.9.0'
})("js1.8.0")).toBe(false);

expect(ClientSDK.compatible({
js: '>=1.9.0'
})(undefined)).toBe(true);
})
})
110 changes: 110 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,4 +666,114 @@ describe('Cloud Code', () => {
done();
});
});

it_exclude_dbs(['postgres'])('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
var TestObject = Parse.Object.extend('TestObject');
var NoBeforeSaveObject = Parse.Object.extend('NoBeforeSave');
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');

Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
var object = req.object;
object.set('before', 'save');
res.success();
});

Parse.Cloud.define('removeme', (req, res) => {
var testObject = new TestObject();
testObject.save()
.then(testObject => {
var object = new NoBeforeSaveObject({remove: testObject});
return object.save();
})
.then(object => {
object.unset('remove');
return object.save();
})
.then(object => {
res.success(object);
});
});

Parse.Cloud.define('removeme2', (req, res) => {
var testObject = new TestObject();
testObject.save()
.then(testObject => {
var object = new BeforeSaveObject({remove: testObject});
return object.save();
})
.then(object => {
object.unset('remove');
return object.save();
})
.then(object => {
res.success(object);
});
});

Parse.Cloud.run('removeme')
.then(aNoBeforeSaveObj => {
expect(aNoBeforeSaveObj.get('remove')).toEqual(undefined);

return Parse.Cloud.run('removeme2');
})
.then(aBeforeSaveObj => {
expect(aBeforeSaveObj.get('before')).toEqual('save');
expect(aBeforeSaveObj.get('remove')).toEqual(undefined);
done();
});
});

it_exclude_dbs(['postgres'])('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
var TestObject = Parse.Object.extend('TestObject');
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');

Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
var object = req.object;
object.set('before', 'save');
object.unset('remove');
res.success();
});

let object;
let testObject = new TestObject({key: 'value'});
testObject.save().then(() => {
object = new BeforeSaveObject();
return object.save().then(() => {
object.set({remove:testObject})
return object.save();
});
}).then((objectAgain) => {
expect(objectAgain.get('remove')).toBeUndefined();
expect(object.get('remove')).toBeUndefined();
done();
}).fail((err) => {
console.error(err);
done();
})
});

it_exclude_dbs(['postgres'])('should not include relation op (regression test for #1606)', done => {
var TestObject = Parse.Object.extend('TestObject');
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
let testObj;
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
var object = req.object;
object.set('before', 'save');
testObj = new TestObject();
testObj.save().then(() => {
object.relation('testsRelation').add(testObj);
res.success();
})
});

let object = new BeforeSaveObject();
object.save().then((objectAgain) => {
// Originally it would throw as it would be a non-relation
expect(() => { objectAgain.relation('testsRelation') }).not.toThrow();
done();
}).fail((err) => {
console.error(err);
done();
})
});
});
20 changes: 0 additions & 20 deletions spec/Middlewares.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,4 @@ describe('middlewares', () => {
});
});
});

it('should properly parse the SDK versions', () => {
let clientSDKFromVersion = middlewares.clientSDKFromVersion;
expect(clientSDKFromVersion('i1.1.1')).toEqual({
sdk: 'i',
version: '1.1.1'
});
expect(clientSDKFromVersion('i1')).toEqual({
sdk: 'i',
version: '1'
});
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
sdk: 'apple-tv',
version: '1.13.0'
});
expect(clientSDKFromVersion('js1.9.0')).toEqual({
sdk: 'js',
version: '1.9.0'
});
})
});
40 changes: 40 additions & 0 deletions src/ClientSDK.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
var semver = require('semver');

function compatible(compatibleSDK) {
return function(clientSDK) {
if (typeof clientSDK === 'string') {
clientSDK = fromString(clientSDK);
}
// REST API, or custom SDK
if (!clientSDK) {
return true;
}
let clientVersion = clientSDK.version;
let compatiblityVersion = compatibleSDK[clientSDK.sdk];
return semver.satisfies(clientVersion, compatiblityVersion);
}
}

function supportsForwardDelete(clientSDK) {
return compatible({
js: '>=1.9.0'
})(clientSDK);
}

function fromString(version) {
let versionRE = /([-a-zA-Z]+)([0-9\.]+)/;
let match = version.toLowerCase().match(versionRE);
if (match && match.length === 3) {
return {
sdk: match[1],
version: match[2]
}
}
return undefined;
}

module.exports = {
compatible,
supportsForwardDelete,
fromString
}
28 changes: 22 additions & 6 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var cryptoUtils = require('./cryptoUtils');
var passwordCrypto = require('./password');
var Parse = require('parse/node');
var triggers = require('./triggers');
var ClientSDK = require('./ClientSDK');
import RestQuery from './RestQuery';
import _ from 'lodash';

Expand Down Expand Up @@ -763,9 +764,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
.then(response => {
response.updatedAt = this.updatedAt;
if (this.storage.changedByTrigger) {
Object.keys(this.data).forEach(fieldName => {
response[fieldName] = response[fieldName] || this.data[fieldName];
});
this.updateResponseWithData(response, this.data);
}
this.response = { response };
});
Expand Down Expand Up @@ -823,9 +822,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
response.username = this.data.username;
}
if (this.storage.changedByTrigger) {
Object.keys(this.data).forEach(fieldName => {
response[fieldName] = response[fieldName] || this.data[fieldName];
});
this.updateResponseWithData(response, this.data);
}
this.response = {
status: 201,
Expand Down Expand Up @@ -914,5 +911,24 @@ RestWrite.prototype.cleanUserAuthData = function() {
}
};

RestWrite.prototype.updateResponseWithData = function(response, data) {
let clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK);
Object.keys(data).forEach(fieldName => {
let dataValue = data[fieldName];
let responseValue = response[fieldName];

response[fieldName] = responseValue || dataValue;

// Strips operations from responses
if (response[fieldName] && response[fieldName].__op) {
delete response[fieldName];
if (clientSupportsDelete && dataValue.__op == 'Delete') {
response[fieldName] = dataValue;
}
}
});
return response;
}

export default RestWrite;
module.exports = RestWrite;
15 changes: 2 additions & 13 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,7 @@ var Parse = require('parse/node').Parse;

var auth = require('./Auth');
var Config = require('./Config');

function clientSDKFromVersion(version) {
let versionRE = /([-a-zA-Z]+)([0-9\.]+)/;
let match = version.toLowerCase().match(versionRE);
if (match && match.length === 3) {
return {
sdk: match[1],
version: match[2]
}
}
}
var ClientSDK = require('./ClientSDK');

// Checks that the request is authorized for this app and checks user
// auth too.
Expand Down Expand Up @@ -106,7 +96,7 @@ function handleParseHeaders(req, res, next) {
}

if (info.clientVersion) {
info.clientSDK = clientSDKFromVersion(info.clientVersion);
info.clientSDK = ClientSDK.fromString(info.clientVersion);
}

if (fileViaJSON) {
Expand Down Expand Up @@ -300,5 +290,4 @@ module.exports = {
handleParseHeaders: handleParseHeaders,
enforceMasterKeyAccess: enforceMasterKeyAccess,
promiseEnforceMasterKeyAccess,
clientSDKFromVersion
};