Skip to content
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

fix: set objects in afterFind triggers #7311

Merged
merged 10 commits into from
Oct 9, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ ___
- EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#6891](https://github.com/parse-community/parse-server/issues/6891)
- EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/issues/7231)
### Other Changes
- Allow afterFind and afterLiveQueryEvent to set pointers (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)
Expand Down
19 changes: 19 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,25 @@ describe('afterFind hooks', () => {
});
});

it('can set a pointer object in afterFind', async done => {
const obj = new Parse.Object('MyObject');
await obj.save();
Parse.Cloud.afterFind('MyObject', async ({ objects }) => {
const otherObject = new Parse.Object('Test');
otherObject.set('foo', 'bar');
await otherObject.save();
objects[0].set('Pointer', otherObject);
expect(objects[0].get('Pointer').get('foo')).toBe('bar');
return objects;
});
const query = new Parse.Query('MyObject');
query.equalTo('objectId', obj.id);
const [obj2] = await query.find();
const pointer = obj2.get('Pointer');
expect(pointer.get('foo')).toBe('bar');
done();
});

it('should have request headers', done => {
Parse.Cloud.afterFind('MyObject', req => {
expect(req.headers).toBeDefined();
Expand Down
38 changes: 38 additions & 0 deletions spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,44 @@ describe('ParseLiveQuery', function () {
await object.save();
});

it('can handle afterEvent set pointers', async done => {
await reconfigureServer({
liveQuery: {
classNames: ['TestObject'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
});

const object = new TestObject();
await object.save();

const secondObject = new Parse.Object('Test2');
secondObject.set('foo', 'bar');
await secondObject.save();

Parse.Cloud.afterLiveQueryEvent('TestObject', async ({ object }) => {
const query = new Parse.Query('Test2');
const obj = await query.first();
object.set('obj', obj);
});

const query = new Parse.Query(TestObject);
query.equalTo('objectId', object.id);
const subscription = await query.subscribe();
subscription.on('update', object => {
expect(object.get('obj')).toBeDefined();
expect(object.get('obj').get('foo')).toBe('bar');
done();
});
subscription.on('error', () => {
fail('error should not have been called.');
});
object.set({ foo: 'bar' });
await object.save();
});

it('can handle async afterEvent modification', async done => {
await reconfigureServer({
liveQuery: {
Expand Down
15 changes: 7 additions & 8 deletions src/LiveQuery/ParseLiveQueryServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ParsePubSub } from './ParsePubSub';
import SchemaController from '../Controllers/SchemaController';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { runLiveQueryEventHandlers, getTrigger, runTrigger } from '../triggers';
import { runLiveQueryEventHandlers, getTrigger, runTrigger, toJSONwithObjects } from '../triggers';
import { getAuthForSessionToken, Auth } from '../Auth';
import { getCacheController } from '../Controllers';
import LRU from 'lru-cache';
Expand Down Expand Up @@ -181,8 +181,7 @@ class ParseLiveQueryServer {
return;
}
if (res.object && typeof res.object.toJSON === 'function') {
deletedParseObject = res.object.toJSON();
deletedParseObject.className = className;
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
}
client.pushDelete(requestId, deletedParseObject);
} catch (error) {
Expand Down Expand Up @@ -326,13 +325,13 @@ class ParseLiveQueryServer {
return;
}
if (res.object && typeof res.object.toJSON === 'function') {
currentParseObject = res.object.toJSON();
currentParseObject.className = res.object.className || className;
currentParseObject = toJSONwithObjects(res.object, res.object.className || className);
}

if (res.original && typeof res.original.toJSON === 'function') {
originalParseObject = res.original.toJSON();
originalParseObject.className = res.original.className || className;
originalParseObject = toJSONwithObjects(
res.original,
res.original.className || className
);
}
const functionName =
'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
Expand Down
24 changes: 23 additions & 1 deletion src/triggers.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,28 @@ export function _unregisterAll() {
Object.keys(_triggerStore).forEach(appId => delete _triggerStore[appId]);
}

export function toJSONwithObjects(object, className) {
dblythy marked this conversation as resolved.
Show resolved Hide resolved
if (!object || !object.toJSON) {
return {};
dblythy marked this conversation as resolved.
Show resolved Hide resolved
}
const toJSON = object.toJSON();
for (const key of Object.keys(toJSON)) {
const val = toJSON[key];
if (!val || !val.__type || val.__type !== 'Pointer') {
continue;
}
const pointer = object.get(key);
const json = pointer.toJSON();
json.className = pointer.className;
json.__type = 'Object';
toJSON[key] = json;
}
if (className) {
toJSON.className = className;
}
return toJSON;
}

export function getTrigger(className, triggerType, applicationId) {
if (!applicationId) {
throw 'Missing ApplicationID';
Expand Down Expand Up @@ -316,7 +338,7 @@ export function getResponseObject(request, resolve, reject) {
response = request.objects;
}
response = response.map(object => {
return object.toJSON();
return toJSONwithObjects(object);
});
return resolve(response);
}
Expand Down